├── .github └── workflows │ ├── build-core.yml │ ├── build-serialization-jsonnet.yml │ └── build-serialization-textjson.yml ├── .gitignore ├── LICENSE ├── README.md └── src ├── FastIDs.TypeId.Serialization ├── FastIDs.TypeId.Serialization.sln ├── TypeId.Serialization.JsonNet.Tests │ ├── TypeId.Serialization.JsonNet.Tests.csproj │ ├── TypeIdDecodedSerializationTests.cs │ ├── TypeIdSerializationTests.cs │ └── Usings.cs ├── TypeId.Serialization.JsonNet │ ├── Extensions.cs │ ├── TypeId.Serialization.JsonNet.csproj │ ├── TypeIdConverter.cs │ └── TypeIdDecodedConverter.cs ├── TypeId.Serialization.SystemTextJson.Tests │ ├── TypeId.Serialization.SystemTextJson.Tests.csproj │ ├── TypeIdDecodedSerializationTests.cs │ ├── TypeIdSerializationTests.cs │ └── Usings.cs └── TypeId.Serialization.SystemTextJson │ ├── Extensions.cs │ ├── TypeId.Serialization.SystemTextJson.csproj │ ├── TypeIdConverter.cs │ └── TypeIdDecodedConverter.cs └── FastIDs.TypeId ├── FastIDs.TypeId.sln ├── TypeId.Benchmarks ├── InternalBenchmarks │ ├── FormatUuidBenchmarks.cs │ ├── IdAlphabetValidationBenchmarks.cs │ └── TypeAlphabetValidationBenchmarks.cs ├── LibraryComparison │ ├── TypeIdComparison.cs │ ├── TypeIdGeneration.cs │ ├── TypeIdParsing.cs │ ├── TypeIdRetrieveFlow.cs │ └── TypeIdString.cs ├── Program.cs └── TypeId.Benchmarks.csproj ├── TypeId.Core ├── AssemblyInfo.cs ├── Base32.cs ├── Base32Constants.cs ├── TypeId.Core.csproj ├── TypeId.cs ├── TypeIdConstants.cs ├── TypeIdDecoded.cs ├── TypeIdDecodedComparers.cs ├── TypeIdParser.cs └── Uuid │ ├── GuidConverter.cs │ ├── UuidComparer.cs │ ├── UuidDecoder.cs │ └── UuidGenerator.cs └── TypeId.Tests ├── TypeId.Tests.csproj ├── TypeIdTests ├── ComparisonTests.cs ├── FormattingTests.cs ├── GenerationTests.cs ├── IdConvertTests.cs ├── ParsingTests.cs ├── TestCases.cs ├── TimestampTests.cs └── TypeMatchingTests.cs └── Usings.cs /.github/workflows/build-core.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: Build Core 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - 'typeid-core-v*' 12 | paths: 13 | - 'src/FastIDs.TypeId/**' 14 | pull_request: 15 | branches: 16 | - main 17 | paths: 18 | - 'src/FastIDs.TypeId/**' 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | defaults: 24 | run: 25 | working-directory: ./src/FastIDs.TypeId 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Setup .NET 29 | uses: actions/setup-dotnet@v4 30 | with: 31 | dotnet-version: 8 32 | - name: Restore dependencies 33 | run: dotnet restore 34 | - name: Build 35 | run: dotnet build --no-restore --configuration Release 36 | - name: Test 37 | run: dotnet test --no-restore --no-build --configuration Release 38 | - name: Publish to NuGet 39 | if: startsWith(github.ref, 'refs/tags/') 40 | run: | 41 | dotnet pack --no-build -c Release ./TypeId.Core -o . 42 | dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate 43 | -------------------------------------------------------------------------------- /.github/workflows/build-serialization-jsonnet.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: Build Json.NET Serialization 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - 'typeid-jsonnet-v*' 12 | paths: 13 | - 'src/FastIDs.TypeId.Serialization/**' 14 | pull_request: 15 | branches: 16 | - main 17 | paths: 18 | - 'src/FastIDs.TypeId.Serialization/**' 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | defaults: 24 | run: 25 | working-directory: ./src/FastIDs.TypeId.Serialization 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Setup .NET 29 | uses: actions/setup-dotnet@v4 30 | with: 31 | dotnet-version: 8 32 | - name: Restore dependencies 33 | run: dotnet restore ./TypeId.Serialization.JsonNet/TypeId.Serialization.JsonNet.csproj 34 | - name: Build 35 | run: dotnet build --no-restore --configuration Release ./TypeId.Serialization.JsonNet/TypeId.Serialization.JsonNet.csproj 36 | - name: Test 37 | run: dotnet test --configuration Release ./TypeId.Serialization.JsonNet.Tests/TypeId.Serialization.JsonNet.Tests.csproj 38 | - name: Publish to NuGet 39 | if: startsWith(github.ref, 'refs/tags/') 40 | run: | 41 | dotnet pack --no-build -c Release ./TypeId.Serialization.JsonNet -o . 42 | dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate 43 | -------------------------------------------------------------------------------- /.github/workflows/build-serialization-textjson.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: Build System.Text.Json Serialization 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - 'typeid-textjson-v*' 12 | paths: 13 | - 'src/FastIDs.TypeId.Serialization/**' 14 | pull_request: 15 | branches: 16 | - main 17 | paths: 18 | - 'src/FastIDs.TypeId.Serialization/**' 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | defaults: 24 | run: 25 | working-directory: ./src/FastIDs.TypeId.Serialization 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Setup .NET 29 | uses: actions/setup-dotnet@v4 30 | with: 31 | dotnet-version: 8 32 | - name: Restore dependencies 33 | run: dotnet restore ./TypeId.Serialization.SystemTextJson/TypeId.Serialization.SystemTextJson.csproj 34 | - name: Build 35 | run: dotnet build --no-restore --configuration Release ./TypeId.Serialization.SystemTextJson/TypeId.Serialization.SystemTextJson.csproj 36 | - name: Test 37 | run: dotnet test --configuration Release ./TypeId.Serialization.SystemTextJson.Tests/TypeId.Serialization.SystemTextJson.Tests.csproj 38 | - name: Publish to NuGet 39 | if: startsWith(github.ref, 'refs/tags/') 40 | run: | 41 | dotnet pack --no-build -c Release ./TypeId.Serialization.SystemTextJson -o . 42 | dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # Visual Studio per-user settings data 3 | ###################################### 4 | *.suo 5 | *.user 6 | **/.idea/** 7 | 8 | #################### 9 | # Build/Test folders 10 | #################### 11 | 12 | **/bin/ 13 | **/obj/ 14 | **/TestResults/ 15 | **/Temp/ 16 | **/buildlogs/ 17 | **/.vs/ 18 | 19 | **/*project.lock.json 20 | **/project.lock.json 21 | **/*.nuspec 22 | 23 | sdk/packages/** 24 | sdk/test/PCLTests/AndroidTests/packages/** 25 | sdk/.vs/** 26 | 27 | **/fxcop-report-*.xml 28 | 29 | docgenerator/.vs/** 30 | 31 | Deployment/** 32 | NuGetDownloads/** 33 | DocDeployment/** 34 | DocBuildAssemblies/** 35 | Include/** 36 | 37 | **/*.userprefs 38 | /sdk/test/CrossPlatformTests/CommonTests/Resources/settings.json 39 | packages 40 | /sdk/test/Unity/Unity3DTests/Assets/Resources/settings.json 41 | /sdk/test/Unity/Unity3DTests/Assets/Resources/awsconfig.xml 42 | /sdk/test/Unity/Unity3DTests/Unity3DTests.CSharp.csproj 43 | /sdk/test/Unity/Unity3DTests/Unity3DTests.CSharp.Editor.csproj 44 | /sdk/test/Unity/Unity3DTests/Unity3DTests.sln 45 | /sdk/test/Unity/Unity3DTests/Library 46 | /sdk/test/Unity/Unity3DTests/Assets/SDK/*.dll 47 | /sdk/test/Unity/Unity3DTests/Assets/SDK/*.pdb 48 | 49 | *.bak 50 | generator/.vs/** 51 | *.mdb 52 | *.apk 53 | *.meta 54 | /sdk/test/Unity/AWSAndroidHelper/.settings 55 | /sdk/test/Unity3DTests/Assets/SDK/*.mdb 56 | 57 | **/*.partial.sln 58 | **/*.partial.FxCop 59 | **/*.partial.csproj 60 | 61 | .vscode 62 | 63 | global.json 64 | 65 | # BenchmarkDotNet artifacts 66 | src/**/BenchmarkDotNet.Artifacts/** -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeId (Spec v0.3.0) 2 | 3 | [![NuGet Version](https://img.shields.io/nuget/v/FastIDs.TypeId)](https://www.nuget.org/packages/FastIDs.TypeId) 4 | 5 | High-performance C# implementation of [TypeId](https://github.com/jetpack-io/typeid/). 6 | 7 | Here's an example of a TypeID of type user: 8 | ``` 9 | user_2x4y6z8a0b1c2d3e4f5g6h7j8k 10 | └──┘ └────────────────────────┘ 11 | type uuid suffix (base32) 12 | ``` 13 | 14 | ## Why another library? 15 | 16 | This implementation is comparable or faster (sometimes 3x faster) in all common scenarios than other .NET implementations. 17 | It also allocates up to 5x less memory, reducing GC pressure. 18 | 19 | See the [Benchmarks wiki](https://github.com/firenero/TypeId/wiki/Benchmarks) for more details. 20 | 21 | ### Why should you care? 22 | 23 | You may think that generating, parsing, or serializing a single TypeId is very fast regardless of the implementation. 24 | To some degree, that's true. But small inefficiencies accumulate quickly, and they are very hard to spot in a large system. 25 | Most likely, there are millions of IDs parsed and serialized across your whole application daily. 26 | There is no single place with "slow" performance to spot in the profiler, so it's very hard to notice these inefficiencies. 27 | 28 | GC is another important factor. 29 | If every small library generates tons of short-lived objects for no reason, the GC will trigger much more frequently, impacting your whole application. 30 | The tricky part? There is nothing you can do about it because the memory is allocated inside 3rd party code. 31 | 32 | There is no reason to use inefficient building blocks in your application. 33 | With this library, you get the same high-level, easy-to-use API. 34 | You don't have to deal with "weird" performance-oriented approaches. 35 | However, I plan to expose additional performance-oriented APIs in the near future for those who need them. 36 | 37 | ## Installation 38 | 39 | Install from NuGet: https://www.nuget.org/packages/FastIDs.TypeId 40 | 41 | ## Usage 42 | 43 | The library exposes two types optimized for slightly different use-cases. For better understanding refer to [TypeId vs TypeIdDecoded](https://github.com/firenero/TypeId/wiki/Choosing-the-right-type:-TypeId-vs.-TypeIdDecoded) section in wiki. 44 | 45 | This readme covers the basic operation you need to know to use the library. 46 | 47 | ### Import 48 | 49 | ```csharp 50 | using FastIDs.TypeId; 51 | ``` 52 | 53 | ### Creating a TypeId 54 | 55 | TypeId can be generated using static methods of `TypeId` and `TypeIdDecoded` classes. Both have the same API and will create an instance of `TypeIdDecoded` struct. Examples in this section only use `TypeId` for simplicity. 56 | 57 | Generate new ID: 58 | ```csharp 59 | var typeIdDecoded = TypeId.New("prefix"); 60 | ``` 61 | 62 | It's also possible to create a TypeID without a prefix by passing the empty string: 63 | ```csharp 64 | var typeIdDecodedWithoutPrefix = TypeId.New(""); 65 | ``` 66 | *Note: If the prefix is empty, the separator `_` is omitted in the string representation.* 67 | 68 | Create TypeId from existing UUIDv7: 69 | ```csharp 70 | Guid uuidV7 = new Guid("01890a5d-ac96-774b-bcce-b302099a8057"); 71 | var typeIdDecoded = TypeId.FromUuidV7("prefix", uuidV7); 72 | ``` 73 | 74 | Both `TypeId.New(prefix)` and `TypeId.FromUuidV7(prefix, guid)` validate provided prefix. 75 | You can skip this validation by using overloads with `bool validateType` parameter set to `false`. 76 | The best case to do so is when you're 100% sure your prefix is correct and you want to squeeze extra bits of performance. 77 | ```csharp 78 | var typeIdDecoded = TypeId.New("prefix", false) // skips validation and creates a valid TypeId instance. 79 | 80 | var invalidTypeIdDecoded = TypeId.New("123", false) // doesn't throw FormatException despite invalid type provided 81 | var shouldThrowIdDecoded = TypeId.New("123") // throws FormatException 82 | ``` 83 | 84 | ### Conversion between `TypeId` and `TypeIdDecoded` 85 | 86 | Convert `TypeIdDecoded` to `TypeId`: 87 | ```csharp 88 | TypeId typeId = typeIdDecoded.Encode(); 89 | ``` 90 | 91 | Convert `TypeId` to `TypeIdDecoded`: 92 | ```csharp 93 | TypeIdDecoded typeIdDecoded = typeId.Decode(); 94 | ``` 95 | 96 | ### `TypeId` serialization to string 97 | 98 | All the following examples assume that there is a `typeId` variable created this way: 99 | ```csharp 100 | TypeId typeId = TypeId.FromUuidV7("type", new Guid("01890a5d-ac96-774b-bcce-b302099a8057")).Encode(); 101 | ``` 102 | 103 | Get string representation 104 | ```csharp 105 | string typeIdString = typeId.ToString(); 106 | // returns "type_01h455vb4pex5vsknk084sn02q" (without quotes) 107 | ``` 108 | 109 | It's possible to get only type: 110 | ```csharp 111 | ReadOnlySpan typeSpan = typeId.Type; 112 | string type = typeSpan.ToString(); 113 | // both are "type" (without quotes) 114 | ``` 115 | 116 | It's also possible to get only the suffix: 117 | ```csharp 118 | ReadOnlySpan suffixSpan = typeId.Suffix; 119 | string suffix = suffixSpan.ToString(); 120 | // both are "01h455vb4pex5vsknk084sn02q" (without quotes) 121 | ``` 122 | 123 | *Note: if TypeID doesn't have a type (i.e. type is an empty string), values returned from `Suffix` and `ToString()` are equal.* 124 | ```csharp 125 | var typeId = TypeId.FromUuidV7("", new Guid("01890a5d-ac96-774b-bcce-b302099a8057")).Encode(); 126 | Console.WriteLine($"{typeId.Suffix.ToString()} == {typeId.ToString()}"); 127 | // prints: "01h455vb4pex5vsknk084sn02q == 01h455vb4pex5vsknk084sn02q" (without quotes) 128 | ``` 129 | 130 | 131 | ### `TypeIdDecoded` serialization to string 132 | 133 | All the following examples assume that there is a `typeIdDecoded` variable created this way: 134 | ```csharp 135 | TypeIdDecoded typeIdDecoded = TypeId.FromUuidV7("type", new Guid("01890a5d-ac96-774b-bcce-b302099a8057")); 136 | ``` 137 | 138 | Get string representation 139 | ```csharp 140 | string typeIdString = typeIdDecoded.ToString(); 141 | // returns "type_01h455vb4pex5vsknk084sn02q" (without quotes) 142 | ``` 143 | 144 | It's possible to get only type: 145 | ```csharp 146 | string type = typeIdDecoded.Type; 147 | // returns "type" (without quotes) 148 | ``` 149 | 150 | It's also possible to get only the suffix: 151 | ```csharp 152 | string suffix = typeIdDecoded.GetSuffix(); 153 | // returns "01h455vb4pex5vsknk084sn02q" (without quotes) 154 | 155 | Span suffixSpan = stackalloc char[26]; 156 | int charsWritten = typeIdDecoded.GetSuffix(suffixSpan); 157 | // `charsWritten` is 26, and `suffixSpan` contains "01h455vb4pex5vsknk084sn02q" (without quotes) 158 | ``` 159 | 160 | 161 | *Note: if TypeID doesn't have a type (i.e. type is an empty string), values returned from `GetSuffix()` and `ToString()` are equal.* 162 | ```csharp 163 | var typeIdDecoded = TypeId.FromUuidV7("", new Guid("01890a5d-ac96-774b-bcce-b302099a8057")); 164 | Console.WriteLine($"{typeIdDecoded.GetSuffix()} == {typeIdDecoded.ToString()}"); 165 | // prints: "01h455vb4pex5vsknk084sn02q == 01h455vb4pex5vsknk084sn02q" (without quotes) 166 | ``` 167 | 168 | ### Parsing 169 | 170 | String representation can only be parsed into `TypeId`. 171 | 172 | Parse existing string representation to the `TypeId` instance: 173 | ```csharp 174 | TypeId typeId = TypeId.Parse("type_01h455vb4pex5vsknk084sn02q"); 175 | ``` 176 | 177 | The `Parse(string input)` method will throw a `FormatException` in case of incorrect format of the passed value. Use the `TryParse(string input, out TypeId result)` method to avoid throwing the exception: 178 | ```csharp 179 | if (TypeId.TryParse("type_01h455vb4pex5vsknk084sn02q", out TypeId typeId)) 180 | // TypeId is successfully parsed here. 181 | else 182 | // Unable to parse TypeId from the provided string. 183 | ``` 184 | 185 | ### Match type 186 | 187 | Both `TypeId` and `TypeIdDecoded` have the same API for checking if the type equals the provided value. 188 | 189 | ```csharp 190 | bool isSameType = typeId.HasType("your_type"); // also has overload for ReadOnlySpan 191 | ``` 192 | 193 | ### Equality 194 | 195 | Both `TypeId` and `TypeIdDecoded` structs implement the `IEquatable` interface with all its benefits: 196 | * `typeId.Equals(other)` or `typeId == other` to check if IDs are same. 197 | * `!typeId.Equals(other)` or `typeId != other` to check if IDs are different. 198 | * Use `TypeId` as a key in `Dictionary` or `HashSet`. 199 | 200 | ### UUIDv7 component operations 201 | 202 | `TypeIdDecoded` provides the API for accessing the UUIDv7 component of the TypeID. 203 | 204 | Get `Guid`: 205 | ```csharp 206 | Guid uuidv7 = typeIdDecoded.Id; 207 | ``` 208 | 209 | Get the creation timestamp (part of the UUIDv7 component): 210 | ```csharp 211 | DateTimeOffset timestamp = typeIdDecoded.GetTimestamp(); 212 | ``` 213 | 214 | ### Json Serialization 215 | 216 | NuGet packages are available for working with JSON. 217 | 218 | - [System.Text.Json](https://www.nuget.org/packages/FastIDs.TypeId.Serialization.SystemTextJson) 219 | - [Json.Net](https://www.nuget.org/packages/FastIDs.TypeId.Serialization.SystemTextJson) 220 | 221 | You can use the extension method `ConfigureForTypeId` on the `JsonSerializerSettings` type for `Json.Net` or on the `JsonSerializerOptions` type for `System.Text.Json` to automatically serialize a `TypeId` or a `TypeIdDecoded` to a string. 222 | 223 | If you are using [SwashBuckle](https://github.com/domaindrivendev/Swashbuckle.AspNetCore), you will need to configure your service as follows: 224 | 225 | ```cs 226 | builder.Services.AddSwaggerGen(c => 227 | { 228 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "api", Version = "v1" }); 229 | 230 | c.MapType(typeof(TypeId), () => new OpenApiSchema { Type = "string", Example = new OpenApiString("prefix_01h93ech7jf5ktdwg6ye383x34") }); 231 | c.MapType(typeof(TypeIdDecoded), () => new OpenApiSchema { Type = "string", Example = new OpenApiString("prefix_01h93ech7jf5ktdwg6ye383x34") }); 232 | }); 233 | ``` 234 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/FastIDs.TypeId.Serialization.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Serialization.JsonNet", "TypeId.Serialization.JsonNet\TypeId.Serialization.JsonNet.csproj", "{034983AB-AAD4-4430-9F03-879739CFF3F4}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Serialization.JsonNet.Tests", "TypeId.Serialization.JsonNet.Tests\TypeId.Serialization.JsonNet.Tests.csproj", "{0B8600CA-1ACA-4DF9-9734-3E279659B99B}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Serialization.SystemTextJson", "TypeId.Serialization.SystemTextJson\TypeId.Serialization.SystemTextJson.csproj", "{D64D19BF-065C-4736-8BA3-64C7D334E518}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Serialization.SystemTextJson.Tests", "TypeId.Serialization.SystemTextJson.Tests\TypeId.Serialization.SystemTextJson.Tests.csproj", "{F8B698FA-C04C-43CB-BE8F-A2D870147831}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Release|Any CPU = Release|Any CPU 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {034983AB-AAD4-4430-9F03-879739CFF3F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {034983AB-AAD4-4430-9F03-879739CFF3F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {034983AB-AAD4-4430-9F03-879739CFF3F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {034983AB-AAD4-4430-9F03-879739CFF3F4}.Release|Any CPU.Build.0 = Release|Any CPU 21 | {0B8600CA-1ACA-4DF9-9734-3E279659B99B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {0B8600CA-1ACA-4DF9-9734-3E279659B99B}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {0B8600CA-1ACA-4DF9-9734-3E279659B99B}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {0B8600CA-1ACA-4DF9-9734-3E279659B99B}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {D64D19BF-065C-4736-8BA3-64C7D334E518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {D64D19BF-065C-4736-8BA3-64C7D334E518}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {D64D19BF-065C-4736-8BA3-64C7D334E518}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {D64D19BF-065C-4736-8BA3-64C7D334E518}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {F8B698FA-C04C-43CB-BE8F-A2D870147831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {F8B698FA-C04C-43CB-BE8F-A2D870147831}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {F8B698FA-C04C-43CB-BE8F-A2D870147831}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {F8B698FA-C04C-43CB-BE8F-A2D870147831}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet.Tests/TypeId.Serialization.JsonNet.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | FastIDs.TypeId.Serialization.JsonNet.Tests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet.Tests/TypeIdDecodedSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Newtonsoft.Json; 3 | 4 | namespace FastIDs.TypeId.Serialization.JsonNet.Tests; 5 | 6 | [TestFixture] 7 | public class TypeIdDecodedSerializationTests 8 | { 9 | private readonly JsonSerializerSettings _settings = new JsonSerializerSettings().ConfigureForTypeId(); 10 | private const string TypeIdStr = "type_01h455vb4pex5vsknk084sn02q"; 11 | 12 | [Test] 13 | public void TypeId_Plain_Serialized() 14 | { 15 | var json = JsonConvert.SerializeObject(TypeId.Parse(TypeIdStr).Decode(), _settings); 16 | 17 | json.Should().Be($"\"{TypeIdStr}\""); 18 | } 19 | 20 | [Test] 21 | public void TypeId_NestedProperty_Serialized() 22 | { 23 | var obj = new TypeIdContainer(TypeId.Parse(TypeIdStr).Decode(), 42); 24 | var json = JsonConvert.SerializeObject(obj, _settings); 25 | 26 | json.Should().Be($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}"); 27 | } 28 | 29 | [Test] 30 | public void TypeId_Collection_Serialized() 31 | { 32 | var obj = new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr).Decode(), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs").Decode() }); 33 | var json = JsonConvert.SerializeObject(obj, _settings); 34 | 35 | json.Should().Be($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}"); 36 | } 37 | 38 | [Test] 39 | public void TypeId_Plain_Deserialized() 40 | { 41 | var typeId = JsonConvert.DeserializeObject($"\"{TypeIdStr}\"", _settings); 42 | 43 | typeId.Should().Be(TypeId.Parse(TypeIdStr).Decode()); 44 | } 45 | 46 | [Test] 47 | public void TypeId_NestedProperty_Deserialized() 48 | { 49 | var obj = JsonConvert.DeserializeObject($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}", _settings); 50 | 51 | obj.Should().Be(new TypeIdContainer(TypeId.Parse(TypeIdStr).Decode(), 42)); 52 | } 53 | 54 | [Test] 55 | public void TypeId_Collection_Deserialized() 56 | { 57 | var obj = JsonConvert.DeserializeObject($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}", _settings); 58 | 59 | obj.Should().BeEquivalentTo(new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr).Decode(), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs").Decode() })); 60 | } 61 | 62 | private record TypeIdContainer(TypeIdDecoded Id, int Value); 63 | 64 | private record TypeIdArrayContainer(TypeIdDecoded[] Items); 65 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet.Tests/TypeIdSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Newtonsoft.Json; 3 | 4 | namespace FastIDs.TypeId.Serialization.JsonNet.Tests; 5 | 6 | [TestFixture] 7 | public class TypeIdSerializationTests 8 | { 9 | private readonly JsonSerializerSettings _settings = new JsonSerializerSettings().ConfigureForTypeId(); 10 | private const string TypeIdStr = "type_01h455vb4pex5vsknk084sn02q"; 11 | 12 | [Test] 13 | public void TypeId_Plain_Serialized() 14 | { 15 | var json = JsonConvert.SerializeObject(TypeId.Parse(TypeIdStr), _settings); 16 | 17 | json.Should().Be($"\"{TypeIdStr}\""); 18 | } 19 | 20 | [Test] 21 | public void TypeId_NestedProperty_Serialized() 22 | { 23 | var obj = new TypeIdContainer(TypeId.Parse(TypeIdStr), 42); 24 | var json = JsonConvert.SerializeObject(obj, _settings); 25 | 26 | json.Should().Be($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}"); 27 | } 28 | 29 | [Test] 30 | public void TypeId_Collection_Serialized() 31 | { 32 | var obj = new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs") }); 33 | var json = JsonConvert.SerializeObject(obj, _settings); 34 | 35 | json.Should().Be($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}"); 36 | } 37 | 38 | [Test] 39 | public void TypeId_Plain_Deserialized() 40 | { 41 | var typeId = JsonConvert.DeserializeObject($"\"{TypeIdStr}\"", _settings); 42 | 43 | typeId.Should().Be(TypeId.Parse(TypeIdStr)); 44 | } 45 | 46 | [Test] 47 | public void TypeId_NestedProperty_Deserialized() 48 | { 49 | var obj = JsonConvert.DeserializeObject($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}", _settings); 50 | 51 | obj.Should().Be(new TypeIdContainer(TypeId.Parse(TypeIdStr), 42)); 52 | } 53 | 54 | [Test] 55 | public void TypeId_Collection_Deserialized() 56 | { 57 | var obj = JsonConvert.DeserializeObject($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}", _settings); 58 | 59 | obj.Should().BeEquivalentTo(new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs") })); 60 | } 61 | 62 | private record TypeIdContainer(TypeId Id, int Value); 63 | 64 | private record TypeIdArrayContainer(TypeId[] Items); 65 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace FastIDs.TypeId.Serialization.JsonNet; 4 | 5 | public static class Extensions 6 | { 7 | public static JsonSerializerSettings ConfigureForTypeId(this JsonSerializerSettings settings) 8 | { 9 | settings.Converters.Add(new TypeIdConverter()); 10 | settings.Converters.Add(new TypeIdDecodedConverter()); 11 | return settings; 12 | } 13 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet/TypeId.Serialization.JsonNet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | FastIDs.TypeId.Serialization.JsonNet 8 | 9 | FastIDs.TypeId.Serialization.JsonNet 10 | Json.NET serialization helpers for FastIDs.TypeId 11 | Apache-2.0 12 | guid,uuid,id,typeid,type-id,uuid7,identifiers,json,jsonnet,serialization 13 | https://github.com/firenero/TypeId 14 | README.md 15 | Mykhailo Matviiv 16 | true 17 | true 18 | snupkg 19 | Copyright (c) Mykhailo Matviiv 2023. 20 | 21 | 22 | 23 | All 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | 45 | typeid-jsonnet-v 46 | true 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet/TypeIdConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace FastIDs.TypeId.Serialization.JsonNet; 4 | 5 | public class TypeIdConverter : JsonConverter 6 | { 7 | public override void WriteJson(JsonWriter writer, TypeId value, JsonSerializer serializer) 8 | { 9 | writer.WriteValue(value.ToString()); 10 | } 11 | 12 | public override TypeId ReadJson(JsonReader reader, Type objectType, TypeId existingValue, bool hasExistingValue, JsonSerializer serializer) 13 | { 14 | return reader.Value is string val ? TypeId.Parse(val) : default; 15 | } 16 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.JsonNet/TypeIdDecodedConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace FastIDs.TypeId.Serialization.JsonNet; 4 | 5 | public class TypeIdDecodedConverter : JsonConverter 6 | { 7 | public override void WriteJson(JsonWriter writer, TypeIdDecoded value, JsonSerializer serializer) 8 | { 9 | writer.WriteValue(value.ToString()); 10 | } 11 | 12 | public override TypeIdDecoded ReadJson(JsonReader reader, Type objectType, TypeIdDecoded existingValue, bool hasExistingValue, JsonSerializer serializer) 13 | { 14 | return reader.Value is string val ? TypeId.Parse(val).Decode() : default; 15 | } 16 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson.Tests/TypeId.Serialization.SystemTextJson.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | FastIDs.TypeId.Serialization.SystemTextJson.Tests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson.Tests/TypeIdDecodedSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using FluentAssertions; 3 | 4 | namespace FastIDs.TypeId.Serialization.SystemTextJson.Tests; 5 | 6 | [TestFixture] 7 | public class TypeIdDecodedSerializationTests 8 | { 9 | private readonly JsonSerializerOptions _options = new JsonSerializerOptions().ConfigureForTypeId(); 10 | private const string TypeIdStr = "type_01h455vb4pex5vsknk084sn02q"; 11 | 12 | [Test] 13 | public void TypeId_Plain_Serialized() 14 | { 15 | var json = JsonSerializer.Serialize(TypeId.Parse(TypeIdStr).Decode(), _options); 16 | 17 | json.Should().Be($"\"{TypeIdStr}\""); 18 | } 19 | 20 | [Test] 21 | public void TypeId_NestedProperty_Serialized() 22 | { 23 | var obj = new TypeIdDecodedContainer(TypeId.Parse(TypeIdStr).Decode(), 42); 24 | var json = JsonSerializer.Serialize(obj, _options); 25 | 26 | json.Should().Be($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}"); 27 | } 28 | 29 | [Test] 30 | public void TypeId_Collection_Serialized() 31 | { 32 | var obj = new TypeIdDecodedArrayContainer(new[] { TypeId.Parse(TypeIdStr).Decode(), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs").Decode() }); 33 | var json = JsonSerializer.Serialize(obj, _options); 34 | 35 | json.Should().Be($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}"); 36 | } 37 | 38 | [Test] 39 | public void TypeId_Plain_Deserialized() 40 | { 41 | var typeId = JsonSerializer.Deserialize($"\"{TypeIdStr}\"", _options); 42 | 43 | typeId.Should().Be(TypeId.Parse(TypeIdStr).Decode()); 44 | } 45 | 46 | [Test] 47 | public void TypeId_NestedProperty_Deserialized() 48 | { 49 | var obj = JsonSerializer.Deserialize($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}", _options); 50 | 51 | obj.Should().Be(new TypeIdDecodedContainer(TypeId.Parse(TypeIdStr).Decode(), 42)); 52 | } 53 | 54 | [Test] 55 | public void TypeId_Collection_Deserialized() 56 | { 57 | var obj = JsonSerializer.Deserialize($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}", _options); 58 | 59 | obj.Should().BeEquivalentTo(new TypeIdDecodedArrayContainer(new[] { TypeId.Parse(TypeIdStr).Decode(), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs").Decode() })); 60 | } 61 | 62 | [Test] 63 | public void TypeId_DictionaryKey_Serialized() 64 | { 65 | var obj = new Dictionary { { TypeId.Parse(TypeIdStr).Decode(), "Test" } }; 66 | 67 | var json = JsonSerializer.Serialize(obj, _options); 68 | 69 | json.Should().Be($"{{\"{TypeIdStr}\":\"Test\"}}"); 70 | } 71 | 72 | [Test] 73 | public void TypeId_DictionaryKey_DeSerialized() 74 | { 75 | var obj = JsonSerializer.Deserialize>($"{{\"{TypeIdStr}\":\"Test\"}}", _options); 76 | 77 | obj.Should().BeEquivalentTo(new Dictionary { { TypeId.Parse(TypeIdStr).Decode(), "Test" } }); 78 | } 79 | 80 | private record TypeIdDecodedContainer(TypeIdDecoded Id, int Value); 81 | 82 | private record TypeIdDecodedArrayContainer(TypeIdDecoded[] Items); 83 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson.Tests/TypeIdSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using FluentAssertions; 3 | 4 | namespace FastIDs.TypeId.Serialization.SystemTextJson.Tests; 5 | 6 | [TestFixture] 7 | public class TypeIdSerializationTests 8 | { 9 | private readonly JsonSerializerOptions _options = new JsonSerializerOptions().ConfigureForTypeId(); 10 | private const string TypeIdStr = "type_01h455vb4pex5vsknk084sn02q"; 11 | 12 | [Test] 13 | public void TypeId_Plain_Serialized() 14 | { 15 | var json = JsonSerializer.Serialize(TypeId.Parse(TypeIdStr), _options); 16 | 17 | json.Should().Be($"\"{TypeIdStr}\""); 18 | } 19 | 20 | [Test] 21 | public void TypeId_NestedProperty_Serialized() 22 | { 23 | var obj = new TypeIdContainer(TypeId.Parse(TypeIdStr), 42); 24 | var json = JsonSerializer.Serialize(obj, _options); 25 | 26 | json.Should().Be($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}"); 27 | } 28 | 29 | [Test] 30 | public void TypeId_Collection_Serialized() 31 | { 32 | var obj = new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs") }); 33 | var json = JsonSerializer.Serialize(obj, _options); 34 | 35 | json.Should().Be($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}"); 36 | } 37 | 38 | [Test] 39 | public void TypeId_Plain_Deserialized() 40 | { 41 | var typeId = JsonSerializer.Deserialize($"\"{TypeIdStr}\"", _options); 42 | 43 | typeId.Should().Be(TypeId.Parse(TypeIdStr)); 44 | } 45 | 46 | [Test] 47 | public void TypeId_NestedProperty_Deserialized() 48 | { 49 | var obj = JsonSerializer.Deserialize($"{{\"Id\":\"{TypeIdStr}\",\"Value\":42}}", _options); 50 | 51 | obj.Should().Be(new TypeIdContainer(TypeId.Parse(TypeIdStr), 42)); 52 | } 53 | 54 | [Test] 55 | public void TypeId_Collection_Deserialized() 56 | { 57 | var obj = JsonSerializer.Deserialize($"{{\"Items\":[\"{TypeIdStr}\",\"prefix_0123456789abcdefghjkmnpqrs\"]}}", _options); 58 | 59 | obj.Should().BeEquivalentTo(new TypeIdArrayContainer(new[] { TypeId.Parse(TypeIdStr), TypeId.Parse("prefix_0123456789abcdefghjkmnpqrs") })); 60 | } 61 | 62 | [Test] 63 | public void TypeId_DictionaryKey_Serialized() 64 | { 65 | var obj = new Dictionary { { TypeId.Parse(TypeIdStr), "Test"} }; 66 | 67 | var json = JsonSerializer.Serialize(obj, _options); 68 | 69 | json.Should().Be($"{{\"{TypeIdStr}\":\"Test\"}}"); 70 | } 71 | 72 | [Test] 73 | public void TypeId_DictionaryKey_DeSerialized() 74 | { 75 | var obj = JsonSerializer.Deserialize>($"{{\"{TypeIdStr}\":\"Test\"}}", _options); 76 | 77 | obj.Should().BeEquivalentTo(new Dictionary { { TypeId.Parse(TypeIdStr), "Test" } }); 78 | } 79 | 80 | private record TypeIdContainer(TypeId Id, int Value); 81 | 82 | private record TypeIdArrayContainer(TypeId[] Items); 83 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace FastIDs.TypeId.Serialization.SystemTextJson; 4 | 5 | public static class Extensions 6 | { 7 | public static JsonSerializerOptions ConfigureForTypeId(this JsonSerializerOptions options) 8 | { 9 | options.Converters.Add(new TypeIdConverter()); 10 | options.Converters.Add(new TypeIdDecodedConverter()); 11 | return options; 12 | } 13 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson/TypeId.Serialization.SystemTextJson.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | FastIDs.TypeId.Serialization.SystemTextJson 8 | 9 | FastIDs.TypeId.Serialization.SystemTextJson 10 | System.Text.Json serialization helpers for FastIDs.TypeId 11 | Apache-2.0 12 | guid,uuid,id,typeid,type-id,uuid7,identifiers,json,jsonnet,serialization 13 | https://github.com/firenero/TypeId 14 | README.md 15 | Mykhailo Matviiv 16 | true 17 | true 18 | snupkg 19 | Copyright (c) Mykhailo Matviiv 2023. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | All 28 | true 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | 45 | typeid-textjson-v 46 | true 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson/TypeIdConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace FastIDs.TypeId.Serialization.SystemTextJson; 5 | 6 | public class TypeIdConverter : JsonConverter 7 | { 8 | public override TypeId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | var val = reader.GetString(); 11 | return val is not null ? TypeId.Parse(val) : default; 12 | } 13 | 14 | public override void Write(Utf8JsonWriter writer, TypeId value, JsonSerializerOptions options) 15 | { 16 | writer.WriteStringValue(value.ToString()); 17 | } 18 | 19 | public override void WriteAsPropertyName(Utf8JsonWriter writer, TypeId value, JsonSerializerOptions options) 20 | { 21 | writer.WritePropertyName(value.ToString()); 22 | } 23 | 24 | public override TypeId ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 25 | { 26 | var val = reader.GetString(); 27 | return val is not null ? TypeId.Parse(val) : default; 28 | } 29 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId.Serialization/TypeId.Serialization.SystemTextJson/TypeIdDecodedConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace FastIDs.TypeId.Serialization.SystemTextJson; 5 | 6 | public class TypeIdDecodedConverter : JsonConverter 7 | { 8 | public override TypeIdDecoded Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return ReadTypeId(ref reader); 11 | } 12 | 13 | public override void Write(Utf8JsonWriter writer, TypeIdDecoded value, JsonSerializerOptions options) 14 | { 15 | var totalLength = value.Type.Length + 1 + 26; 16 | Span buffer = stackalloc char[totalLength]; 17 | 18 | CopyValueToBuffer(value, buffer); 19 | 20 | writer.WriteStringValue(buffer); 21 | } 22 | 23 | public override TypeIdDecoded ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 24 | { 25 | return ReadTypeId(ref reader); 26 | } 27 | 28 | public override void WriteAsPropertyName(Utf8JsonWriter writer, TypeIdDecoded value, JsonSerializerOptions options) 29 | { 30 | var totalLength = value.Type.Length + 1 + 26; 31 | Span buffer = stackalloc char[totalLength]; 32 | 33 | CopyValueToBuffer(value, buffer); 34 | 35 | writer.WritePropertyName(buffer); 36 | } 37 | 38 | private static TypeIdDecoded ReadTypeId(ref Utf8JsonReader reader) 39 | { 40 | var val = reader.GetString(); 41 | return val is not null ? TypeId.Parse(val).Decode() : default; 42 | } 43 | 44 | private static void CopyValueToBuffer(in TypeIdDecoded value, Span buffer) 45 | { 46 | value.Type.AsSpan().CopyTo(buffer); 47 | buffer[value.Type.Length] = '_'; 48 | value.GetSuffix(buffer[(value.Type.Length + 1)..]); 49 | } 50 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/FastIDs.TypeId.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Core", "TypeId.Core\TypeId.Core.csproj", "{EB1955A2-D8AD-405A-BD92-FD30C8217C13}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Tests", "TypeId.Tests\TypeId.Tests.csproj", "{87B18719-CB5B-494B-9009-C825FF2B3984}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeId.Benchmarks", "TypeId.Benchmarks\TypeId.Benchmarks.csproj", "{6B112FAC-0310-448C-8654-BE41A7FC39CC}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {EB1955A2-D8AD-405A-BD92-FD30C8217C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {EB1955A2-D8AD-405A-BD92-FD30C8217C13}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {EB1955A2-D8AD-405A-BD92-FD30C8217C13}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {EB1955A2-D8AD-405A-BD92-FD30C8217C13}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {87B18719-CB5B-494B-9009-C825FF2B3984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {87B18719-CB5B-494B-9009-C825FF2B3984}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {87B18719-CB5B-494B-9009-C825FF2B3984}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {87B18719-CB5B-494B-9009-C825FF2B3984}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {6B112FAC-0310-448C-8654-BE41A7FC39CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {6B112FAC-0310-448C-8654-BE41A7FC39CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {6B112FAC-0310-448C-8654-BE41A7FC39CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {6B112FAC-0310-448C-8654-BE41A7FC39CC}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/InternalBenchmarks/FormatUuidBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Binary; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace FastIDs.TypeId.Benchmarks.InternalBenchmarks; 5 | 6 | [MemoryDiagnoser] 7 | public class FormatUuidBenchmarks 8 | { 9 | private Guid _guid = new Guid("01890a5d-ac96-774b-bcce-b302099a8057"); 10 | 11 | [Benchmark(Baseline = true)] 12 | public int SpanReverse() 13 | { 14 | Span bytes = stackalloc byte[16]; 15 | _guid.TryWriteBytes(bytes); 16 | 17 | for (var i = 0; i < 1_000_000; i++) 18 | { 19 | bytes[..4].Reverse(); 20 | (bytes[4], bytes[5]) = (bytes[5], bytes[4]); 21 | (bytes[6], bytes[7]) = (bytes[7], bytes[6]); 22 | } 23 | 24 | return bytes[3] + bytes[5]; 25 | } 26 | 27 | [Benchmark] 28 | public int SpanSwap() 29 | { 30 | Span bytes = stackalloc byte[16]; 31 | _guid.TryWriteBytes(bytes); 32 | 33 | for (var i = 0; i < 1_000_000; i++) 34 | { 35 | (bytes[0], bytes[3]) = (bytes[3], bytes[0]); 36 | (bytes[1], bytes[2]) = (bytes[2], bytes[1]); 37 | (bytes[4], bytes[5]) = (bytes[5], bytes[4]); 38 | (bytes[6], bytes[7]) = (bytes[7], bytes[6]); 39 | } 40 | 41 | return bytes[3] + bytes[5]; 42 | } 43 | 44 | [Benchmark] 45 | public int SpanPrimitives() 46 | { 47 | Span bytes = stackalloc byte[16]; 48 | _guid.TryWriteBytes(bytes); 49 | 50 | for (var i = 0; i < 1_000_000; i++) 51 | { 52 | var a = BinaryPrimitives.ReverseEndianness(BitConverter.ToUInt32(bytes[..4])); 53 | BitConverter.TryWriteBytes(bytes[..4], a); 54 | 55 | (bytes[4], bytes[5]) = (bytes[5], bytes[4]); 56 | (bytes[6], bytes[7]) = (bytes[7], bytes[6]); 57 | } 58 | 59 | return bytes[3] + bytes[5]; 60 | } 61 | 62 | [Benchmark] 63 | public int SpanPrimitivesFull() 64 | { 65 | Span bytes = stackalloc byte[16]; 66 | _guid.TryWriteBytes(bytes); 67 | 68 | for (var i = 0; i < 1_000_000; i++) 69 | { 70 | var a = BinaryPrimitives.ReverseEndianness(BitConverter.ToUInt32(bytes[..4])); 71 | BitConverter.TryWriteBytes(bytes[..4], a); 72 | 73 | var b = BinaryPrimitives.ReverseEndianness(BitConverter.ToUInt16(bytes[4..6])); 74 | BitConverter.TryWriteBytes(bytes[4..6], b); 75 | 76 | var c = BinaryPrimitives.ReverseEndianness(BitConverter.ToUInt16(bytes[6..8])); 77 | BitConverter.TryWriteBytes(bytes[6..8], c); 78 | } 79 | 80 | return bytes[3] + bytes[5]; 81 | } 82 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/InternalBenchmarks/IdAlphabetValidationBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Runtime.CompilerServices; 3 | using BenchmarkDotNet.Attributes; 4 | 5 | namespace FastIDs.TypeId.Benchmarks.InternalBenchmarks; 6 | 7 | [MemoryDiagnoser] 8 | [MarkdownExporter] 9 | [MarkdownExporterAttribute.Default] 10 | [MarkdownExporterAttribute.GitHub] 11 | [GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)] 12 | public class IdAlphabetValidationBenchmarks 13 | { 14 | private const string AlphabetStr = "0123456789abcdefghjkmnpqrstvwxyz"; 15 | 16 | private readonly SearchValues _searchValues = SearchValues.Create(AlphabetStr); 17 | private readonly HashSet _alphabetSet = new(AlphabetStr); 18 | 19 | private string[] _validIds = []; 20 | private string[] _invalidIds = []; 21 | 22 | [GlobalSetup] 23 | public void Setup() 24 | { 25 | const int idsCount = 100_000; 26 | _validIds = new string[idsCount]; 27 | _invalidIds = new string[idsCount]; 28 | for (var i = 0; i < idsCount; i++) 29 | { 30 | var id = TypeId.New("", false).ToString(); 31 | _validIds[i] = id; 32 | _invalidIds[i] = id[..^1] + ','; // invalid char 33 | 34 | } 35 | } 36 | 37 | [Benchmark(Baseline = true)] 38 | [BenchmarkCategory("Valid")] 39 | public bool CharCheckUnrolledValid() 40 | { 41 | var isValid = false; 42 | foreach (var id in _validIds) 43 | { 44 | isValid &= IsValidAlphabet(id); 45 | } 46 | 47 | return isValid; 48 | } 49 | 50 | [Benchmark(Baseline = true)] 51 | [BenchmarkCategory("Invalid")] 52 | public bool CharCheckUnrolledInvalid() 53 | { 54 | var isValid = false; 55 | foreach (var id in _invalidIds) 56 | { 57 | isValid &= IsValidAlphabet(id); 58 | } 59 | 60 | return isValid; 61 | } 62 | 63 | [Benchmark] 64 | [BenchmarkCategory("Valid")] 65 | public bool SearchValuesLoopValid() 66 | { 67 | var isValid = false; 68 | foreach (var id in _validIds) 69 | { 70 | isValid &= IsValidAlphabetSearchValues(id); 71 | } 72 | 73 | return isValid; 74 | } 75 | 76 | [Benchmark] 77 | [BenchmarkCategory("Valid")] 78 | public bool SearchValuesUnrollValid() 79 | { 80 | var isValid = false; 81 | foreach (var id in _validIds) 82 | { 83 | isValid &= IsValidAlphabetSearchValuesUnrolled(id); 84 | } 85 | 86 | return isValid; 87 | } 88 | 89 | [Benchmark] 90 | [BenchmarkCategory("Invalid")] 91 | public bool SearchValuesLoopInvalid() 92 | { 93 | var isValid = false; 94 | foreach (var id in _invalidIds) 95 | { 96 | isValid &= IsValidAlphabetSearchValues(id); 97 | } 98 | 99 | return isValid; 100 | } 101 | 102 | [Benchmark] 103 | [BenchmarkCategory("Invalid")] 104 | public bool SearchValuesUnrollInvalid() 105 | { 106 | var isValid = false; 107 | foreach (var id in _invalidIds) 108 | { 109 | isValid &= IsValidAlphabetSearchValuesUnrolled(id); 110 | } 111 | 112 | return isValid; 113 | } 114 | 115 | [Benchmark] 116 | [BenchmarkCategory("Valid")] 117 | public bool HashSetLoopValid() 118 | { 119 | var isValid = false; 120 | foreach (var id in _validIds) 121 | { 122 | isValid &= IsValidAlphabetHashSet(id); 123 | } 124 | 125 | return isValid; 126 | } 127 | 128 | [Benchmark] 129 | [BenchmarkCategory("Valid")] 130 | public bool HashSetUnrollValid() 131 | { 132 | var isValid = false; 133 | foreach (var id in _validIds) 134 | { 135 | isValid &= IsValidAlphabetHashSetUnrolled(id); 136 | } 137 | 138 | return isValid; 139 | } 140 | 141 | [Benchmark] 142 | [BenchmarkCategory("Invalid")] 143 | public bool HashSetLoopInvalid() 144 | { 145 | var isValid = false; 146 | foreach (var id in _invalidIds) 147 | { 148 | isValid &= IsValidAlphabetHashSet(id); 149 | } 150 | 151 | return isValid; 152 | } 153 | 154 | [Benchmark] 155 | [BenchmarkCategory("Invalid")] 156 | public bool HashSetUnrollInvalid() 157 | { 158 | var isValid = false; 159 | foreach (var id in _invalidIds) 160 | { 161 | isValid &= IsValidAlphabetHashSetUnrolled(id); 162 | } 163 | 164 | return isValid; 165 | } 166 | 167 | private static bool IsValidAlphabet(ReadOnlySpan chars) => 168 | IsValidChar(chars[0]) 169 | && IsValidChar(chars[1]) 170 | && IsValidChar(chars[2]) 171 | && IsValidChar(chars[3]) 172 | && IsValidChar(chars[4]) 173 | && IsValidChar(chars[5]) 174 | && IsValidChar(chars[6]) 175 | && IsValidChar(chars[7]) 176 | && IsValidChar(chars[8]) 177 | && IsValidChar(chars[9]) 178 | && IsValidChar(chars[10]) 179 | && IsValidChar(chars[11]) 180 | && IsValidChar(chars[12]) 181 | && IsValidChar(chars[13]) 182 | && IsValidChar(chars[14]) 183 | && IsValidChar(chars[15]) 184 | && IsValidChar(chars[16]) 185 | && IsValidChar(chars[17]) 186 | && IsValidChar(chars[18]) 187 | && IsValidChar(chars[19]) 188 | && IsValidChar(chars[20]) 189 | && IsValidChar(chars[21]) 190 | && IsValidChar(chars[22]) 191 | && IsValidChar(chars[23]) 192 | && IsValidChar(chars[24]) 193 | && IsValidChar(chars[25]); 194 | 195 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 196 | private static bool IsValidChar(char c) 197 | { 198 | if (c >= '0' && c <= '9') 199 | return true; 200 | 201 | return c is >= 'a' and <= 'h' 202 | or >= 'j' and <= 'k' 203 | or >= 'm' and <= 'n' 204 | or >= 'p' and <= 't' 205 | or >= 'v' and <= 'z'; 206 | } 207 | 208 | private bool IsValidAlphabetSearchValues(ReadOnlySpan chars) 209 | { 210 | foreach (var c in chars) 211 | { 212 | if (!_searchValues.Contains(c)) 213 | return false; 214 | } 215 | 216 | return true; 217 | } 218 | 219 | private bool IsValidAlphabetSearchValuesUnrolled(ReadOnlySpan chars) => 220 | _searchValues.Contains(chars[0]) 221 | && _searchValues.Contains(chars[1]) 222 | && _searchValues.Contains(chars[2]) 223 | && _searchValues.Contains(chars[3]) 224 | && _searchValues.Contains(chars[4]) 225 | && _searchValues.Contains(chars[5]) 226 | && _searchValues.Contains(chars[6]) 227 | && _searchValues.Contains(chars[7]) 228 | && _searchValues.Contains(chars[8]) 229 | && _searchValues.Contains(chars[9]) 230 | && _searchValues.Contains(chars[10]) 231 | && _searchValues.Contains(chars[11]) 232 | && _searchValues.Contains(chars[12]) 233 | && _searchValues.Contains(chars[13]) 234 | && _searchValues.Contains(chars[14]) 235 | && _searchValues.Contains(chars[15]) 236 | && _searchValues.Contains(chars[16]) 237 | && _searchValues.Contains(chars[17]) 238 | && _searchValues.Contains(chars[18]) 239 | && _searchValues.Contains(chars[19]) 240 | && _searchValues.Contains(chars[20]) 241 | && _searchValues.Contains(chars[21]) 242 | && _searchValues.Contains(chars[22]) 243 | && _searchValues.Contains(chars[23]) 244 | && _searchValues.Contains(chars[24]) 245 | && _searchValues.Contains(chars[25]); 246 | 247 | private bool IsValidAlphabetHashSet(ReadOnlySpan chars) 248 | { 249 | foreach (var c in chars) 250 | { 251 | if (!_alphabetSet.Contains(c)) 252 | return false; 253 | } 254 | 255 | return true; 256 | } 257 | 258 | private bool IsValidAlphabetHashSetUnrolled(ReadOnlySpan chars) => 259 | _alphabetSet.Contains(chars[0]) 260 | && _alphabetSet.Contains(chars[1]) 261 | && _alphabetSet.Contains(chars[2]) 262 | && _alphabetSet.Contains(chars[3]) 263 | && _alphabetSet.Contains(chars[4]) 264 | && _alphabetSet.Contains(chars[5]) 265 | && _alphabetSet.Contains(chars[6]) 266 | && _alphabetSet.Contains(chars[7]) 267 | && _alphabetSet.Contains(chars[8]) 268 | && _alphabetSet.Contains(chars[9]) 269 | && _alphabetSet.Contains(chars[10]) 270 | && _alphabetSet.Contains(chars[11]) 271 | && _alphabetSet.Contains(chars[12]) 272 | && _alphabetSet.Contains(chars[13]) 273 | && _alphabetSet.Contains(chars[14]) 274 | && _alphabetSet.Contains(chars[15]) 275 | && _alphabetSet.Contains(chars[16]) 276 | && _alphabetSet.Contains(chars[17]) 277 | && _alphabetSet.Contains(chars[18]) 278 | && _alphabetSet.Contains(chars[19]) 279 | && _alphabetSet.Contains(chars[20]) 280 | && _alphabetSet.Contains(chars[21]) 281 | && _alphabetSet.Contains(chars[22]) 282 | && _alphabetSet.Contains(chars[23]) 283 | && _alphabetSet.Contains(chars[24]) 284 | && _alphabetSet.Contains(chars[25]); 285 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/InternalBenchmarks/TypeAlphabetValidationBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Numerics; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using BenchmarkDotNet.Attributes; 6 | 7 | namespace FastIDs.TypeId.Benchmarks.InternalBenchmarks; 8 | 9 | [MemoryDiagnoser] 10 | [MarkdownExporter] 11 | [MarkdownExporterAttribute.Default] 12 | [MarkdownExporterAttribute.GitHub] 13 | public class TypeAlphabetValidationBenchmarks 14 | { 15 | [Params(3, 6, 8, 14, 30, 63)] 16 | public int PrefixLength; 17 | 18 | private string[] _prefixes = []; 19 | 20 | private const string AlphabetStr = "_abcdefghijklmnopqrstuvwxyz"; 21 | private readonly SearchValues _searchValues = SearchValues.Create(AlphabetStr); 22 | private const int UnrollValue = 4; 23 | 24 | [GlobalSetup] 25 | public void Setup() 26 | { 27 | var random = new Random(); 28 | 29 | const int count = 100_000; 30 | _prefixes = new string[count]; 31 | var sb = new StringBuilder(PrefixLength); 32 | 33 | for (var i = 0; i < count; i++) 34 | { 35 | sb.Clear(); 36 | 37 | for (var j = 0; j < PrefixLength; j++) 38 | { 39 | if (j == PrefixLength / 2) 40 | sb.Append('_'); 41 | else 42 | sb.Append((char)random.Next('a', 'z')); 43 | } 44 | 45 | _prefixes[i] = sb.ToString(); 46 | } 47 | } 48 | 49 | [Benchmark(Baseline = true)] 50 | public bool CharCheck() 51 | { 52 | var isValid = false; 53 | foreach (var prefix in _prefixes) 54 | { 55 | foreach (var c in prefix.AsSpan()) 56 | { 57 | isValid &= c is '_' or >= 'a' and <= 'z'; 58 | } 59 | } 60 | 61 | return isValid; 62 | } 63 | 64 | [Benchmark] 65 | public bool CharCheckAscii() 66 | { 67 | var isValid = false; 68 | foreach (var prefix in _prefixes) 69 | { 70 | foreach (var c in prefix.AsSpan()) 71 | { 72 | isValid &= char.IsAsciiLetterLower(c) || c == '_'; 73 | } 74 | } 75 | 76 | return isValid; 77 | } 78 | 79 | [Benchmark] 80 | public bool SearchValuesCheck() 81 | { 82 | var isValid = false; 83 | foreach (var prefix in _prefixes) 84 | { 85 | foreach (var c in prefix.AsSpan()) 86 | { 87 | isValid &= _searchValues.Contains(c); 88 | } 89 | } 90 | 91 | return isValid; 92 | } 93 | 94 | [Benchmark] 95 | public bool SearchValuesContainsAny() 96 | { 97 | var isValid = false; 98 | foreach (var prefix in _prefixes) 99 | { 100 | isValid &= prefix.AsSpan().ContainsAnyExcept(_searchValues); 101 | } 102 | 103 | return isValid; 104 | } 105 | 106 | [Benchmark] 107 | public bool InRangeCheck() 108 | { 109 | var isValid = false; 110 | foreach (var prefix in _prefixes) 111 | { 112 | var span = prefix.AsSpan(); 113 | isValid &= !span.ContainsAnyExceptInRange('_', 'z') && !span.Contains('`'); 114 | } 115 | 116 | return isValid; 117 | } 118 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/LibraryComparison/TypeIdComparison.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using BenchmarkDotNet.Attributes; 3 | using BenchmarkDotNet.Configs; 4 | 5 | namespace FastIDs.TypeId.Benchmarks.LibraryComparison; 6 | 7 | [MemoryDiagnoser] 8 | [MarkdownExporter] 9 | [MarkdownExporterAttribute.Default] 10 | [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] 11 | public class TypeIdComparison 12 | { 13 | [Params(0, 5, 10, 30, 63)] 14 | public int PrefixLength; 15 | 16 | private string _suffix = "01h455vb4pex5vsknk084sn02q"; 17 | 18 | private TypeId[] _fastIdTypeIds = Array.Empty(); 19 | private TypeIdDecoded[] _fastIdTypeIdsDecoded = Array.Empty(); 20 | private TcKs.TypeId.TypeId[] _tcKsTypeIds = Array.Empty(); 21 | private global::TypeId.TypeId[] _cbuctokTypeIds = Array.Empty(); 22 | 23 | private readonly string _prefixFull; 24 | 25 | public TypeIdComparison() 26 | { 27 | var random = new Random(42); 28 | var sb = new StringBuilder(63); 29 | for (var i = 0; i < 63; i++) 30 | { 31 | var letter = (char) random.Next('a', 'z'); 32 | sb.Append(letter); 33 | } 34 | _prefixFull = sb.ToString(); 35 | } 36 | 37 | [GlobalSetup] 38 | public void Setup() 39 | { 40 | var typeIdStr = PrefixLength > 0 41 | ? $"{_prefixFull[..PrefixLength]}_{_suffix}" 42 | : _suffix; 43 | 44 | _fastIdTypeIds = new[] { TypeId.Parse(typeIdStr), TypeId.Parse(typeIdStr) }; 45 | _fastIdTypeIdsDecoded = new[] { TypeId.Parse(typeIdStr).Decode(), TypeId.Parse(typeIdStr).Decode() }; 46 | 47 | _tcKsTypeIds = Array.Empty(); 48 | if (TcKs.TypeId.TypeId.TryParse(typeIdStr, out var parsed)) 49 | { 50 | _tcKsTypeIds = new[] { parsed, parsed }; 51 | } 52 | 53 | _cbuctokTypeIds = new[] { global::TypeId.TypeId.Parse(typeIdStr), global::TypeId.TypeId.Parse(typeIdStr) }; 54 | } 55 | 56 | [Benchmark(Baseline = true)] 57 | [BenchmarkCategory("Equality")] 58 | public bool FastIdsEquals() => _fastIdTypeIds[0] == _fastIdTypeIds[1]; 59 | 60 | [Benchmark] 61 | [BenchmarkCategory("Equality")] 62 | public bool FastIdsDecodedEquals() => _fastIdTypeIdsDecoded[0] == _fastIdTypeIdsDecoded[1]; 63 | 64 | [Benchmark] 65 | [BenchmarkCategory("Equality")] 66 | public bool TcKsEquals() => _tcKsTypeIds[0] == _tcKsTypeIds[1]; 67 | 68 | [Benchmark] 69 | [BenchmarkCategory("Equality")] 70 | public bool CbuctokEquals() => _cbuctokTypeIds[0] == _cbuctokTypeIds[1]; 71 | 72 | [Benchmark(Baseline = true)] 73 | [BenchmarkCategory("HashCode")] 74 | public int FastIdsHash() => _fastIdTypeIds[0].GetHashCode(); 75 | 76 | [Benchmark] 77 | [BenchmarkCategory("HashCode")] 78 | public int FastIdsDecodedHash() => _fastIdTypeIdsDecoded[0].GetHashCode(); 79 | 80 | [Benchmark] 81 | [BenchmarkCategory("HashCode")] 82 | public int TcKsHash() => _tcKsTypeIds[0].GetHashCode(); 83 | 84 | [Benchmark] 85 | [BenchmarkCategory("HashCode")] 86 | public int CbuctokHash() => _cbuctokTypeIds[0].GetHashCode(); 87 | 88 | [Benchmark(Baseline = true)] 89 | [BenchmarkCategory("Prefix")] 90 | public string FastIdsPrefixString() => _fastIdTypeIds[0].Type.ToString(); 91 | 92 | [Benchmark] 93 | [BenchmarkCategory("Prefix")] 94 | public ReadOnlySpan FastIdsPrefixSpan() => _fastIdTypeIds[0].Type; 95 | 96 | [Benchmark] 97 | [BenchmarkCategory("Prefix")] 98 | public string FastIdsDecodedPrefix() => _fastIdTypeIdsDecoded[0].Type; 99 | 100 | [Benchmark] 101 | [BenchmarkCategory("Prefix")] 102 | public string TcKsPrefix() => _tcKsTypeIds[0].Type; 103 | 104 | [Benchmark] 105 | [BenchmarkCategory("Prefix")] 106 | public string CbuctokPrefix() => _cbuctokTypeIds[0].Type; 107 | 108 | [Benchmark(Baseline = true)] 109 | [BenchmarkCategory("Id")] 110 | public Guid FastIdsId() => _fastIdTypeIds[0].Decode().Id; 111 | 112 | [Benchmark] 113 | [BenchmarkCategory("Id")] 114 | public Guid FastIdsDecodedId() => _fastIdTypeIdsDecoded[0].Id; 115 | 116 | [Benchmark] 117 | [BenchmarkCategory("Id")] 118 | public Guid TcKsId() => _tcKsTypeIds[0].Id; 119 | 120 | [Benchmark] 121 | [BenchmarkCategory("Id")] 122 | public string CbuctokId() => _cbuctokTypeIds[0].Id; 123 | 124 | [Benchmark(Baseline = true)] 125 | [BenchmarkCategory("Suffix")] 126 | public string FastIdsSuffixString() => _fastIdTypeIds[0].Suffix.ToString(); 127 | 128 | [Benchmark] 129 | [BenchmarkCategory("Suffix")] 130 | public ReadOnlySpan FastIdsSuffixSpan() => _fastIdTypeIds[0].Suffix; 131 | 132 | [Benchmark] 133 | [BenchmarkCategory("Suffix")] 134 | public string FastIdsDecodedSuffix() => _fastIdTypeIdsDecoded[0].GetSuffix(); 135 | 136 | [Benchmark] 137 | [BenchmarkCategory("Suffix")] 138 | public string TcKsSuffix() => _tcKsTypeIds[0].Suffix; 139 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/LibraryComparison/TypeIdGeneration.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace FastIDs.TypeId.Benchmarks.LibraryComparison; 5 | 6 | [MemoryDiagnoser] 7 | [MarkdownExporter] 8 | [MarkdownExporterAttribute.Default] 9 | public class TypeIdGeneration 10 | { 11 | [Params(0, 5, 10, 30, 63)] 12 | public int PrefixLength; 13 | 14 | private string _prefix = ""; 15 | private readonly string _prefixFull; 16 | 17 | public TypeIdGeneration() 18 | { 19 | var random = new Random(42); 20 | var sb = new StringBuilder(63); 21 | for (var i = 0; i < 63; i++) 22 | { 23 | var letter = (char) random.Next('a', 'z'); 24 | sb.Append(letter); 25 | } 26 | _prefixFull = sb.ToString(); 27 | } 28 | 29 | [GlobalSetup] 30 | public void Setup() 31 | { 32 | _prefix = _prefixFull[..PrefixLength]; 33 | } 34 | 35 | [Benchmark(Baseline = true)] 36 | public TypeIdDecoded FastIdsBenchmark() 37 | { 38 | return TypeId.New(_prefix); 39 | } 40 | 41 | [Benchmark] 42 | public TypeIdDecoded FastIdsNoCheckBenchmark() 43 | { 44 | return TypeId.New(_prefix, false); 45 | } 46 | 47 | [Benchmark] 48 | public TcKs.TypeId.TypeId TcKsBenchmark() 49 | { 50 | return TcKs.TypeId.TypeId.NewId(_prefix); 51 | } 52 | 53 | [Benchmark] 54 | public global::TypeId.TypeId CbuctokBenchmark() 55 | { 56 | return global::TypeId.TypeId.NewTypeId(_prefix); 57 | } 58 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/LibraryComparison/TypeIdParsing.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace FastIDs.TypeId.Benchmarks.LibraryComparison; 5 | 6 | [MemoryDiagnoser] 7 | [MarkdownExporter] 8 | [MarkdownExporterAttribute.Default] 9 | [MarkdownExporterAttribute.GitHub] 10 | public class TypeIdParsing 11 | { 12 | [Params(0, 5, 10, 30, 63)] 13 | public int PrefixLength; 14 | 15 | private string _typeIdString = ""; 16 | 17 | private readonly string _prefixFull; 18 | private readonly Guid _uuidV7; 19 | 20 | public TypeIdParsing() 21 | { 22 | var random = new Random(42); 23 | var sb = new StringBuilder(63); 24 | for (var i = 0; i < 63; i++) 25 | { 26 | var letter = (char) random.Next('a', 'z'); 27 | sb.Append(letter); 28 | } 29 | _prefixFull = sb.ToString(); 30 | _uuidV7 = new Guid("01890a5d-ac96-774b-bcce-b302099a8057"); 31 | } 32 | 33 | [GlobalSetup] 34 | public void Setup() 35 | { 36 | _typeIdString = TypeId.FromUuidV7(_prefixFull[..PrefixLength], _uuidV7).ToString(); 37 | } 38 | 39 | [Benchmark(Baseline = true)] 40 | public TypeId FastIdsParse() 41 | { 42 | return TypeId.Parse(_typeIdString); 43 | } 44 | 45 | [Benchmark] 46 | public TypeId FastIdsTryParse() 47 | { 48 | TypeId.TryParse(_typeIdString, out var typeId); 49 | return typeId; 50 | } 51 | 52 | [Benchmark] 53 | public TcKs.TypeId.TypeId TcKsParse() 54 | { 55 | return TcKs.TypeId.TypeId.Parse(_typeIdString); 56 | } 57 | 58 | [Benchmark] 59 | public TcKs.TypeId.TypeId TcKsTryParse() 60 | { 61 | TcKs.TypeId.TypeId.TryParse(_typeIdString, out var typeId); 62 | return typeId; 63 | } 64 | 65 | [Benchmark] 66 | public global::TypeId.TypeId CbuctokParse() 67 | { 68 | return global::TypeId.TypeId.Parse(_typeIdString); 69 | } 70 | 71 | [Benchmark] 72 | public global::TypeId.TypeId CbuctokTryParse() 73 | { 74 | global::TypeId.TypeId.TryParse(_typeIdString, out var typeId); 75 | return typeId; 76 | } 77 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/LibraryComparison/TypeIdRetrieveFlow.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace FastIDs.TypeId.Benchmarks.LibraryComparison; 5 | 6 | [MemoryDiagnoser] 7 | [MarkdownExporter] 8 | [MarkdownExporterAttribute.Default] 9 | public class TypeIdRetrieveFlow 10 | { 11 | [Params(0, 5, 10, 30, 63)] 12 | public int PrefixLength; 13 | 14 | private string _typeIdString = ""; 15 | 16 | private readonly string _prefixFull; 17 | private readonly Guid _uuidV7; 18 | 19 | public TypeIdRetrieveFlow() 20 | { 21 | var random = new Random(42); 22 | var sb = new StringBuilder(63); 23 | for (var i = 0; i < 63; i++) 24 | { 25 | var letter = (char) random.Next('a', 'z'); 26 | sb.Append(letter); 27 | } 28 | _prefixFull = sb.ToString(); 29 | _uuidV7 = new Guid("01890a5d-ac96-774b-bcce-b302099a8057"); 30 | } 31 | 32 | [GlobalSetup] 33 | public void Setup() 34 | { 35 | _typeIdString = TypeId.FromUuidV7(_prefixFull[..PrefixLength], _uuidV7).ToString(); 36 | } 37 | 38 | [Benchmark(Baseline = true)] 39 | public string FastIds() 40 | { 41 | var typeId = TypeId.Parse(_typeIdString); 42 | return typeId.ToString(); 43 | } 44 | 45 | [Benchmark] 46 | public string FastIdsDecode() 47 | { 48 | var typeId = TypeId.Parse(_typeIdString); 49 | var decoded = typeId.Decode(); 50 | return decoded.ToString(); 51 | } 52 | 53 | [Benchmark] 54 | public string TcKsBenchmark() 55 | { 56 | var typeId = TcKs.TypeId.TypeId.Parse(_typeIdString); 57 | return typeId.ToString(); 58 | } 59 | 60 | [Benchmark] 61 | public string CbuctokBenchmark() 62 | { 63 | var typeId = global::TypeId.TypeId.Parse(_typeIdString); 64 | return typeId.ToString(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/LibraryComparison/TypeIdString.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using BenchmarkDotNet.Attributes; 3 | 4 | namespace FastIDs.TypeId.Benchmarks.LibraryComparison; 5 | 6 | [MemoryDiagnoser] 7 | [MarkdownExporter] 8 | [MarkdownExporterAttribute.Default] 9 | public class TypeIdString 10 | { 11 | [Params(0, 5, 10, 30, 63)] 12 | public int PrefixLength; 13 | 14 | private readonly string _prefixFull; 15 | private readonly Guid _uuidV7; 16 | 17 | private TypeId _fastIdTypeId; 18 | private TypeIdDecoded _fastIdTypeIdDecoded; 19 | private TcKs.TypeId.TypeId _tcKsTypeId; 20 | private global::TypeId.TypeId _cbuctokTypeId; 21 | 22 | public TypeIdString() 23 | { 24 | var random = new Random(42); 25 | var sb = new StringBuilder(63); 26 | for (var i = 0; i < 63; i++) 27 | { 28 | var letter = (char) random.Next('a', 'z'); 29 | sb.Append(letter); 30 | } 31 | _prefixFull = sb.ToString(); 32 | 33 | _uuidV7 = new Guid("01890a5d-ac96-774b-bcce-b302099a8057"); 34 | } 35 | 36 | [GlobalSetup] 37 | public void Setup() 38 | { 39 | var prefix = _prefixFull[..PrefixLength]; 40 | 41 | _fastIdTypeIdDecoded = TypeId.FromUuidV7(prefix, _uuidV7); 42 | _fastIdTypeId = _fastIdTypeIdDecoded.Encode(); 43 | _tcKsTypeId = new TcKs.TypeId.TypeId(prefix, _uuidV7); 44 | _cbuctokTypeId = new global::TypeId.TypeId(prefix, _uuidV7); 45 | } 46 | 47 | [Benchmark(Baseline = true)] 48 | public string FastIdsDecoded() 49 | { 50 | return _fastIdTypeIdDecoded.ToString(); 51 | } 52 | 53 | [Benchmark] 54 | public string FastIdsEncoded() 55 | { 56 | return _fastIdTypeId.ToString(); 57 | } 58 | 59 | [Benchmark] 60 | public string TcKs() 61 | { 62 | return _tcKsTypeId.ToString(); 63 | } 64 | 65 | [Benchmark] 66 | public string Cbuctok() 67 | { 68 | return _cbuctokTypeId.ToString(); 69 | } 70 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using BenchmarkDotNet.Configs; 3 | using BenchmarkDotNet.Jobs; 4 | using BenchmarkDotNet.Running; 5 | 6 | var config = DefaultConfig.Instance; 7 | 8 | // config.AddJob(Job.Default.WithId("Scalar").WithEnvironmentVariable("DOTNET_EnableHWIntrinsic", "0").AsBaseline()) 9 | // .AddJob(Job.Default.WithId("Vector128").WithEnvironmentVariable("DOTNET_EnableAVX512F", "0").WithEnvironmentVariable("DOTNET_EnableAVX2", "0")) 10 | // .AddJob(Job.Default.WithId("Vector256").WithEnvironmentVariable("DOTNET_EnableAVX512F", "0")) 11 | // .AddJob(Job.Default.WithId("Vector512")); 12 | 13 | BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()).Run(config: config); -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Benchmarks/TypeId.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | Nullable 9 | FastIDs.TypeId.Benchmarks 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("TypeId.Tests")] -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Base32.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.CompilerServices; 3 | using System.Text; 4 | 5 | namespace FastIDs.TypeId; 6 | 7 | internal static class Base32 8 | { 9 | public static int Encode(ReadOnlySpan bytes, Span output) 10 | { 11 | ValidateEncodeParams(bytes, output); 12 | 13 | return EncodeImpl(bytes, output, Base32Constants.Alphabet); 14 | } 15 | 16 | public static int Encode(ReadOnlySpan bytes, Span utf8Output) 17 | { 18 | ValidateEncodeParams(bytes, utf8Output); 19 | 20 | return EncodeImpl(bytes, utf8Output, Base32Constants.Utf8Alphabet); 21 | } 22 | 23 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 24 | private static void ValidateEncodeParams(ReadOnlySpan bytes, Span output) where TChar: struct, IBinaryInteger 25 | { 26 | if (bytes.Length != Base32Constants.DecodedLength) 27 | throw new FormatException($"Input must be {Base32Constants.DecodedLength} bytes long."); 28 | if (output.Length < Base32Constants.EncodedLength) 29 | throw new FormatException($"Output must be at least {Base32Constants.EncodedLength} chars long."); 30 | } 31 | 32 | private static int EncodeImpl(ReadOnlySpan bytes, Span output, ReadOnlySpan alpha) where TChar: struct, IBinaryInteger 33 | { 34 | // 10 byte timestamp 35 | output[0] = alpha[(bytes[0] & 224) >> 5]; 36 | output[1] = alpha[bytes[0] & 31]; 37 | output[2] = alpha[(bytes[1] & 248) >> 3]; 38 | output[3] = alpha[((bytes[1] & 7) << 2) | ((bytes[2] & 192) >> 6)]; 39 | output[4] = alpha[(bytes[2] & 62) >> 1]; 40 | output[5] = alpha[((bytes[2] & 1) << 4) | ((bytes[3] & 240) >> 4)]; 41 | output[6] = alpha[((bytes[3] & 15) << 1) | ((bytes[4] & 128) >> 7)]; 42 | output[7] = alpha[(bytes[4] & 124) >> 2]; 43 | output[8] = alpha[((bytes[4] & 3) << 3) | ((bytes[5] & 224) >> 5)]; 44 | output[9] = alpha[bytes[5] & 31]; 45 | 46 | // 16 bytes of entropy 47 | output[10] = alpha[(bytes[6] & 248) >> 3]; 48 | output[11] = alpha[((bytes[6] & 7) << 2) | ((bytes[7] & 192) >> 6)]; 49 | output[12] = alpha[(bytes[7] & 62) >> 1]; 50 | output[13] = alpha[((bytes[7] & 1) << 4) | ((bytes[8] & 240) >> 4)]; 51 | output[14] = alpha[((bytes[8] & 15) << 1) | ((bytes[9] & 128) >> 7)]; 52 | output[15] = alpha[(bytes[9] & 124) >> 2]; 53 | output[16] = alpha[((bytes[9] & 3) << 3) | ((bytes[10] & 224) >> 5)]; 54 | output[17] = alpha[bytes[10] & 31]; 55 | output[18] = alpha[(bytes[11] & 248) >> 3]; 56 | output[19] = alpha[((bytes[11] & 7) << 2) | ((bytes[12] & 192) >> 6)]; 57 | output[20] = alpha[(bytes[12] & 62) >> 1]; 58 | output[21] = alpha[((bytes[12] & 1) << 4) | ((bytes[13] & 240) >> 4)]; 59 | output[22] = alpha[((bytes[13] & 15) << 1) | ((bytes[14] & 128) >> 7)]; 60 | output[23] = alpha[(bytes[14] & 124) >> 2]; 61 | output[24] = alpha[((bytes[14] & 3) << 3) | ((bytes[15] & 224) >> 5)]; 62 | output[25] = alpha[bytes[15] & 31]; 63 | 64 | return Base32Constants.EncodedLength; 65 | } 66 | 67 | public static bool TryDecode(ReadOnlySpan input, Span output) 68 | { 69 | if (input.Length != Base32Constants.EncodedLength) 70 | return false; 71 | 72 | Span inputBytes = stackalloc byte[Base32Constants.EncodedLength]; 73 | var writtenBytesCount = Encoding.UTF8.GetBytes(input, inputBytes); 74 | if (writtenBytesCount != Base32Constants.EncodedLength) 75 | return false; 76 | 77 | if (!IsValidAlphabet(input)) 78 | return false; 79 | 80 | var dec = Base32Constants.DecodingTable; 81 | // 6 bytes timestamp (48 bits) 82 | output[0] = (byte)((dec[inputBytes[0]] << 5) | dec[inputBytes[1]]); 83 | output[1] = (byte)((dec[inputBytes[2]] << 3) | (dec[inputBytes[3]] >> 2)); 84 | output[2] = (byte)((dec[inputBytes[3]] << 6) | (dec[inputBytes[4]] << 1) | (dec[inputBytes[5]] >> 4)); 85 | output[3] = (byte)((dec[inputBytes[5]] << 4) | (dec[inputBytes[6]] >> 1)); 86 | output[4] = (byte)((dec[inputBytes[6]] << 7) | (dec[inputBytes[7]] << 2) | (dec[inputBytes[8]] >> 3)); 87 | output[5] = (byte)((dec[inputBytes[8]] << 5) | dec[inputBytes[9]]); 88 | 89 | // 10 bytes of entropy (80 bits) 90 | output[6] = (byte)((dec[inputBytes[10]] << 3) | (dec[inputBytes[11]] >> 2)); // First 4 bits are the version 91 | output[7] = (byte)((dec[inputBytes[11]] << 6) | (dec[inputBytes[12]] << 1) | (dec[inputBytes[13]] >> 4)); 92 | output[8] = (byte)((dec[inputBytes[13]] << 4) | (dec[inputBytes[14]] >> 1)); // First 2 bits are the variant 93 | output[9] = (byte)((dec[inputBytes[14]] << 7) | (dec[inputBytes[15]] << 2) | (dec[inputBytes[16]] >> 3)); 94 | output[10] = (byte)((dec[inputBytes[16]] << 5) | dec[inputBytes[17]]); 95 | output[11] = (byte)((dec[inputBytes[18]] << 3) | dec[inputBytes[19]] >> 2); 96 | output[12] = (byte)((dec[inputBytes[19]] << 6) | (dec[inputBytes[20]] << 1) | (dec[inputBytes[21]] >> 4)); 97 | output[13] = (byte)((dec[inputBytes[21]] << 4) | (dec[inputBytes[22]] >> 1)); 98 | output[14] = (byte)((dec[inputBytes[22]] << 7) | (dec[inputBytes[23]] << 2) | (dec[inputBytes[24]] >> 3)); 99 | output[15] = (byte)((dec[inputBytes[24]] << 5) | dec[inputBytes[25]]); 100 | 101 | return true; 102 | } 103 | 104 | public static bool IsValid(ReadOnlySpan input) 105 | { 106 | if (input.Length != Base32Constants.EncodedLength) 107 | return false; 108 | 109 | return IsValidAlphabet(input); 110 | } 111 | 112 | private static bool IsValidAlphabet(ReadOnlySpan chars) 113 | { 114 | foreach (var c in chars) 115 | { 116 | if (!Base32Constants.AlphabetValues.Contains(c)) 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Base32Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace FastIDs.TypeId; 4 | 5 | internal static class Base32Constants 6 | { 7 | public const string Alphabet = "0123456789abcdefghjkmnpqrstvwxyz"; 8 | 9 | public static ReadOnlySpan Utf8Alphabet => "0123456789abcdefghjkmnpqrstvwxyz"u8; 10 | 11 | public static readonly SearchValues AlphabetValues = SearchValues.Create(Alphabet); 12 | 13 | public const int DecodedLength = 16; 14 | 15 | public const int EncodedLength = 26; 16 | 17 | public static readonly byte[] DecodingTable = { 18 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 19 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 20 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 21 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 22 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 23 | 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 24 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 25 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 26 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 27 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 28 | 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14, 29 | 0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 30 | 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 31 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 32 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 33 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 34 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 35 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 36 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 37 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 38 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 40 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 41 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 42 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 43 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF 44 | }; 45 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeId.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Nullable 8 | FastIDs.TypeId 9 | TypeId.Core 10 | 11 | FastIDs.TypeId 12 | High-performance TypeId implementation 13 | 14 | High-performance dotnet implementation of type-safe, K-sortable, globally unique identifier inspired by Stripe IDs 15 | 16 | Apache-2.0 17 | guid,uuid,id,typeid,type-id,uuid7,identifiers 18 | https://github.com/firenero/TypeId 19 | README.md 20 | Mykhailo Matviiv 21 | true 22 | true 23 | snupkg 24 | Copyright (c) Mykhailo Matviiv 2023. 25 | 26 | 27 | 28 | All 29 | true 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | 45 | 46 | typeid-core-v 47 | true 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeId.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Text.Unicode; 5 | 6 | namespace FastIDs.TypeId; 7 | 8 | /// 9 | /// Type-safe extension of UUIDv7. 10 | /// 11 | /// 12 | /// Example TypeId format: 13 | /// 14 | /// user_2x4y6z8a0b1c2d3e4f5g6h7j8k 15 | /// └──┘ └────────────────────────┘ 16 | /// type uuid suffix (base32) 17 | /// 18 | /// 19 | [StructLayout(LayoutKind.Auto)] 20 | public readonly struct TypeId : IEquatable, ISpanFormattable, IUtf8SpanFormattable, IComparable, IComparable 21 | { 22 | private readonly string _str; 23 | 24 | internal TypeId(string str) 25 | { 26 | _str = str; 27 | } 28 | 29 | private int SeparatorIndex => _str.Length - TypeIdConstants.IdLength - 1; 30 | 31 | /// 32 | /// Returns a value indicating whether the TypeId has the specified type. 33 | /// 34 | /// The type to compare. 35 | /// true if the TypeId has the specified type; otherwise, false. 36 | public bool HasType(string type) => HasType(type.AsSpan()); 37 | 38 | /// 39 | /// Returns a value indicating whether the TypeId has the specified type. 40 | /// 41 | /// The type to compare. 42 | /// true if the TypeId has the specified type; otherwise, false. 43 | public bool HasType(ReadOnlySpan type) 44 | { 45 | var thisTypeLen = SeparatorIndex > 0 ? SeparatorIndex : 0; 46 | 47 | return type.SequenceEqual(_str.AsSpan(0, thisTypeLen)); 48 | } 49 | 50 | /// 51 | /// Returns a string representation of the TypeId. 52 | /// 53 | /// A string representation of the TypeId. 54 | public override string ToString() => _str; 55 | 56 | /// 57 | /// Returns a string representation of the TypeId. 58 | /// 59 | /// Format string. Can be empty. 60 | /// Format provider. Can be null. 61 | /// Formatted string representation of the TypeId. 62 | /// 63 | /// This method ignores and parameters and outputs the same result as . 64 | /// 65 | public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); 66 | 67 | /// 68 | /// Tries to format the value of the current instance into the provided span of characters. 69 | /// 70 | /// The span in which to write this instance's value formatted as a span of characters. 71 | /// When this method returns, contains the number of characters that were written in . 72 | /// A span containing the characters that represent a standard or custom format string. Can be empty. 73 | /// Format provider. Can be null. 74 | /// true if the formatting was successful; otherwise, false. 75 | /// 76 | /// This method ignores and parameters. 77 | /// 78 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) => 79 | destination.TryWrite($"{_str}", out charsWritten); 80 | 81 | /// 82 | /// Tries to format the value of the current instance into the provided span of bytes in UTF-8 encoding. 83 | /// 84 | /// The span in which to write this instance's value formatted as a span of bytes in UTF-8 encoding. 85 | /// When this method returns, contains the number of bytes that were written in . 86 | /// A span containing the characters that represent a standard or custom format string. Can be empty. 87 | /// Format provider. Can be null. 88 | /// true if the formatting was successful; otherwise, false. 89 | /// 90 | /// This method ignores and parameters. 91 | /// 92 | public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) => 93 | Utf8.TryWrite(utf8Destination, $"{_str}", out bytesWritten); 94 | 95 | /// 96 | /// A type component of the TypeId. 97 | /// 98 | public ReadOnlySpan Type => SeparatorIndex > 0 ? _str.AsSpan(0, SeparatorIndex) : ReadOnlySpan.Empty; 99 | 100 | /// 101 | /// An encoded UUIDv7 component of the TypeId. 102 | /// 103 | public ReadOnlySpan Suffix => _str.AsSpan(SeparatorIndex + 1); 104 | 105 | /// 106 | /// Decodes the TypeId into components struct. 107 | /// 108 | /// Decoded TypeId. 109 | public TypeIdDecoded Decode() 110 | { 111 | var idSpan = _str.AsSpan(SeparatorIndex + 1); 112 | Span decoded = stackalloc byte[Base32Constants.DecodedLength]; 113 | 114 | var canDecode = Base32.TryDecode(idSpan, decoded); 115 | Debug.Assert(canDecode, "Id is not a valid Base32 string. Should never happen because it was validated before."); 116 | 117 | TypeIdParser.FormatUuidBytes(decoded); 118 | 119 | var type = SeparatorIndex > 0 ? _str[..SeparatorIndex] : ""; 120 | return new TypeIdDecoded(type, new Guid(decoded)); 121 | } 122 | 123 | /// 124 | /// Generates new TypeId with the specified type and random UUIDv7. 125 | /// 126 | /// Type of the ID. Can be empty. 127 | /// New TypeId with the specified type and random UUIDv7. 128 | /// Thrown when type is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 129 | /// 130 | /// This method validates the type. If you are sure that type is valid use to skip type validation. 131 | /// 132 | public static TypeIdDecoded New(string type) => TypeIdDecoded.New(type); 133 | 134 | /// 135 | /// Generates new TypeId with the specified type and random UUIDv7. If is false, type is not validated. 136 | /// 137 | /// Type of the ID. Can be empty. 138 | /// If true, type is validated. If false, type is not validated. 139 | /// New TypeId with the specified type and random UUIDv7. 140 | /// Thrown when is set to true and is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 141 | /// 142 | /// Use this method with set to false when you are sure that is valid. 143 | /// This method is a bit faster than (especially for longer types) because it skips type validation. 144 | /// 145 | public static TypeIdDecoded New(string type, bool validateType) => TypeIdDecoded.New(type, validateType); 146 | 147 | /// 148 | /// Generates new TypeId with the specified type and UUIDv7. 149 | /// 150 | /// Type of the ID. Can be empty. 151 | /// UUIDv7 ID part of the TypeId. 152 | /// New TypeId with the specified type and UUIDv7. 153 | /// Thrown when type is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 154 | /// 155 | /// must be a valid UUIDv7. method generates UUIDv4 which is not valid UUIDv7. 156 | ///

157 | /// This method validates the type. If you are sure that type is valid use to skip type validation. 158 | ///
159 | public static TypeIdDecoded FromUuidV7(string type, Guid uuidV7) => TypeIdDecoded.FromUuidV7(type, uuidV7); 160 | 161 | /// 162 | /// Generates new TypeId with the specified type and UUIDv7. If is false, type is not validated. 163 | /// 164 | /// Type of the ID. Can be empty. 165 | /// UUIDv7 ID part of the TypeId. 166 | /// If true, type is validated. If false, type is not validated. 167 | /// New TypeId with the specified type and UUIDv7. 168 | /// Thrown when is set to true and is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 169 | /// 170 | /// must be a valid UUIDv7. method generates UUIDv4 which is not valid UUIDv7. 171 | ///

172 | /// Use this method with set to false when you are sure that is valid. 173 | /// This method is a bit faster than (especially for longer types) because it skips type validation. 174 | ///
175 | public static TypeIdDecoded FromUuidV7(string type, Guid uuidV7, bool validateType) => TypeIdDecoded.FromUuidV7(type, uuidV7, validateType); 176 | 177 | /// 178 | /// Parses the specified string into a TypeId. 179 | /// 180 | /// String representation of the TypeId. 181 | /// TypeId instance. 182 | /// Thrown when the specified string is not a valid TypeId. 183 | /// 184 | /// Example TypeId format: 185 | /// 186 | /// user_2x4y6z8a0b1c2d3e4f5g6h7j8k 187 | /// └──┘ └────────────────────────┘ 188 | /// type uuid suffix (base32) 189 | /// 190 | /// 191 | public static TypeId Parse(string input) 192 | { 193 | var separatorIdx = input.LastIndexOf('_'); 194 | if (separatorIdx == 0) 195 | throw new FormatException("Type separator must be omitted if there is no type present."); 196 | if (separatorIdx > TypeIdConstants.MaxTypeLength) 197 | throw new FormatException($"Type can be at most {TypeIdConstants.MaxTypeLength} characters long."); 198 | 199 | var typeSpan = separatorIdx != -1 ? input.AsSpan(0, separatorIdx) : ReadOnlySpan.Empty; 200 | var typeError = TypeIdParser.ValidateType(typeSpan); 201 | if (typeError is not TypeIdParser.TypeError.None) 202 | throw new FormatException(typeError.ToErrorMessage()); 203 | 204 | var idSpan = input.AsSpan(separatorIdx + 1); 205 | if (idSpan.Length != TypeIdConstants.IdLength) 206 | throw new FormatException($"Id must be {TypeIdConstants.IdLength} characters long."); 207 | if (idSpan[0] > '7') 208 | throw new FormatException("The maximum possible suffix for TypeId is '7zzzzzzzzzzzzzzzzzzzzzzzzz'"); 209 | 210 | if (!Base32.IsValid(idSpan)) 211 | throw new FormatException("Id is not a valid Base32 string."); 212 | 213 | return new(input); 214 | } 215 | 216 | /// 217 | /// Tries to parse the specified string into a TypeId. 218 | /// 219 | /// String representation of the TypeId. 220 | /// Contains parsed TypeId if the method returns true. 221 | /// True if the specified string was successfully parsed into a TypeId; otherwise, false. 222 | /// 223 | /// Example TypeId format: 224 | /// 225 | /// user_2x4y6z8a0b1c2d3e4f5g6h7j8k 226 | /// └──┘ └────────────────────────┘ 227 | /// type uuid suffix (base32) 228 | /// 229 | /// 230 | public static bool TryParse(string input, out TypeId result) 231 | { 232 | var separatorIdx = input.LastIndexOf('_'); 233 | if (separatorIdx is 0 or > TypeIdConstants.MaxTypeLength) 234 | return Error(out result); 235 | 236 | var typeSpan = separatorIdx != -1 ? input.AsSpan(0, separatorIdx) : ReadOnlySpan.Empty; 237 | if (TypeIdParser.ValidateType(typeSpan) is not TypeIdParser.TypeError.None) 238 | return Error(out result); 239 | 240 | var idSpan = input.AsSpan(separatorIdx + 1); 241 | if (idSpan.Length != TypeIdConstants.IdLength || idSpan[0] > '7') 242 | return Error(out result); 243 | 244 | if (!Base32.IsValid(idSpan)) 245 | return Error(out result); 246 | 247 | result = new(input); 248 | return true; 249 | } 250 | 251 | public bool Equals(TypeId other) => _str == other._str; 252 | 253 | public override bool Equals(object? obj) => obj is TypeId other && Equals(other); 254 | 255 | public override int GetHashCode() => _str.GetHashCode(); 256 | 257 | public static bool operator ==(TypeId left, TypeId right) => left.Equals(right); 258 | 259 | public static bool operator !=(TypeId left, TypeId right) => !left.Equals(right); 260 | 261 | public int CompareTo(TypeId other) => string.Compare(_str, other._str, StringComparison.Ordinal); 262 | 263 | public int CompareTo(object? obj) 264 | { 265 | if (ReferenceEquals(null, obj)) 266 | return 1; 267 | 268 | return obj is TypeId other 269 | ? CompareTo(other) 270 | : throw new ArgumentException($"Object must be of type {nameof(TypeId)}"); 271 | } 272 | 273 | 274 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 275 | private static bool Error(out TypeId result) 276 | { 277 | result = default; 278 | return false; 279 | } 280 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeIdConstants.cs: -------------------------------------------------------------------------------- 1 | namespace FastIDs.TypeId; 2 | 3 | internal static class TypeIdConstants 4 | { 5 | public const int MaxTypeLength = 63; 6 | public const int IdLength = 26; 7 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeIdDecoded.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text.Unicode; 3 | using FastIDs.TypeId.Uuid; 4 | 5 | namespace FastIDs.TypeId; 6 | 7 | [StructLayout(LayoutKind.Auto)] 8 | public readonly struct TypeIdDecoded : IEquatable, ISpanFormattable, IUtf8SpanFormattable 9 | { 10 | private static readonly UuidGenerator UuidGenerator = new(); 11 | 12 | /// 13 | /// The type part of the TypeId. 14 | /// 15 | public string Type { get; } 16 | 17 | /// 18 | /// The ID part of the TypeId. 19 | /// 20 | public Guid Id { get; } 21 | 22 | internal TypeIdDecoded(string type, Guid id) 23 | { 24 | Type = type; 25 | Id = id; 26 | } 27 | 28 | /// 29 | /// Returns the ID part of the TypeId as an encoded string. 30 | /// 31 | /// ID part of the TypeId as an encoded string. 32 | public string GetSuffix() 33 | { 34 | Span suffixChars = stackalloc char[Base32Constants.EncodedLength]; 35 | GetSuffix(suffixChars); 36 | return suffixChars.ToString(); 37 | } 38 | 39 | /// 40 | /// Returns the ID part of the TypeId as an encoded string. 41 | /// 42 | /// When this method returns, contains the encoded ID part of the TypeId. 43 | /// Number of characters written to . 44 | public int GetSuffix(Span output) 45 | { 46 | Span idBytes = stackalloc byte[Base32Constants.DecodedLength]; 47 | Id.TryWriteBytes(idBytes); 48 | 49 | TypeIdParser.FormatUuidBytes(idBytes); 50 | 51 | return Base32.Encode(idBytes, output); 52 | } 53 | 54 | public int GetSuffix(Span utf8Output) 55 | { 56 | Span idBytes = stackalloc byte[Base32Constants.DecodedLength]; 57 | Id.TryWriteBytes(idBytes); 58 | 59 | TypeIdParser.FormatUuidBytes(idBytes); 60 | 61 | return Base32.Encode(idBytes, utf8Output); 62 | } 63 | 64 | /// 65 | /// Returns the ID generation timestamp. 66 | /// 67 | /// DateTimeOffset representing the ID generation timestamp. 68 | public DateTimeOffset GetTimestamp() 69 | { 70 | var timestampMs = UuidDecoder.DecodeTimestamp(Id); 71 | return DateTimeOffset.FromUnixTimeMilliseconds(timestampMs); 72 | } 73 | 74 | /// 75 | /// Encodes the TypeId components into a TypeId struct. 76 | /// 77 | /// A TypeId struct that contains the encoded TypeId. 78 | public TypeId Encode() => new(ToString()); 79 | 80 | /// 81 | /// Returns a value indicating whether the TypeId has the specified type. 82 | /// 83 | /// The type to compare. 84 | /// true if the TypeId has the specified type; otherwise, false. 85 | public bool HasType(string type) => type == Type; 86 | 87 | /// 88 | /// Returns a value indicating whether the TypeId has the specified type. 89 | /// 90 | /// The type to compare. 91 | /// true if the TypeId has the specified type; otherwise, false. 92 | public bool HasType(ReadOnlySpan type) => type.Equals(Type.AsSpan(), StringComparison.Ordinal); 93 | 94 | /// 95 | /// Returns a string that represents the TypeId value. 96 | /// 97 | /// Formatted string. 98 | public override string ToString() 99 | { 100 | Span suffixChars = stackalloc char[Base32Constants.EncodedLength]; 101 | GetSuffix(suffixChars); 102 | 103 | return Type.Length != 0 104 | ? $"{Type}_{suffixChars}" 105 | : suffixChars.ToString(); 106 | } 107 | 108 | /// 109 | /// Returns a string that represents the TypeId value. 110 | /// 111 | /// Format string. Can be empty. 112 | /// Format provider. Can be null. 113 | /// Formatted string. 114 | /// 115 | /// This method ignores and parameters and outputs the same result as . 116 | /// 117 | public string ToString(string? format, IFormatProvider? formatProvider) => ToString(); 118 | 119 | /// 120 | /// Tries to format the value of the current instance into the provided span of characters. 121 | /// 122 | /// The span in which to write this instance's value formatted as a span of characters. 123 | /// When this method returns, contains the number of characters that were written in . 124 | /// A span containing the characters that represent a standard or custom format string. Can be empty. 125 | /// Format provider. Can be null. 126 | /// true if the formatting was successful; otherwise, false. 127 | /// 128 | /// This method ignores and parameters. 129 | /// 130 | public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider) 131 | { 132 | charsWritten = 0; 133 | if (Type.Length != 0) 134 | { 135 | if (!destination.TryWrite($"{Type}_", out charsWritten)) 136 | return false; 137 | } 138 | 139 | var suffixSpan = destination[charsWritten..]; 140 | if (suffixSpan.Length < Base32Constants.EncodedLength) 141 | return false; 142 | 143 | var suffixCharsWritten = GetSuffix(suffixSpan); 144 | charsWritten += suffixCharsWritten; 145 | 146 | return true; 147 | } 148 | 149 | /// 150 | /// Tries to format the value of the current instance into the provided span of bytes in UTF-8 encoding. 151 | /// 152 | /// The span in which to write this instance's value formatted as a span of bytes in UTF-8 encoding. 153 | /// When this method returns, contains the number of bytes that were written in . 154 | /// A span containing the characters that represent a standard or custom format string. Can be empty. 155 | /// Format provider. Can be null. 156 | /// true if the formatting was successful; otherwise, false. 157 | /// 158 | /// This method ignores and parameters. 159 | /// 160 | public bool TryFormat(Span utf8Destination, out int bytesWritten, ReadOnlySpan format, IFormatProvider? provider) 161 | { 162 | bytesWritten = 0; 163 | if (Type.Length != 0) 164 | { 165 | if (!Utf8.TryWrite(utf8Destination, $"{Type}_", out bytesWritten)) 166 | return false; 167 | } 168 | 169 | var suffixSpan = utf8Destination[bytesWritten..]; 170 | if (suffixSpan.Length < Base32Constants.EncodedLength) 171 | return false; 172 | 173 | var suffixBytesWritten = GetSuffix(suffixSpan); 174 | bytesWritten += suffixBytesWritten; 175 | 176 | return true; 177 | } 178 | 179 | public bool Equals(TypeIdDecoded other) => Type == other.Type && Id.Equals(other.Id); 180 | 181 | public override bool Equals(object? obj) => obj is TypeIdDecoded other && Equals(other); 182 | 183 | public override int GetHashCode() => HashCode.Combine(Type, Id); 184 | 185 | public static bool operator ==(TypeIdDecoded left, TypeIdDecoded right) => left.Equals(right); 186 | 187 | public static bool operator !=(TypeIdDecoded left, TypeIdDecoded right) => !left.Equals(right); 188 | 189 | /// 190 | /// Generates new TypeId with the specified type and random UUIDv7. 191 | /// 192 | /// Type of the ID. Can be empty. 193 | /// New TypeId with the specified type and random UUIDv7. 194 | /// Thrown when type is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 195 | /// 196 | /// This method validates the type. If you are sure that type is valid use to skip type validation. 197 | /// 198 | public static TypeIdDecoded New(string type) => FromUuidV7(type, UuidGenerator.New()); 199 | 200 | /// 201 | /// Generates new TypeId with the specified type and random UUIDv7. If is false, type is not validated. 202 | /// 203 | /// Type of the ID. Can be empty. 204 | /// If true, type is validated. If false, type is not validated. 205 | /// New TypeId with the specified type and random UUIDv7. 206 | /// Thrown when is set to true and is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 207 | /// 208 | /// Use this method with set to false when you are sure that is valid. 209 | /// This method is a bit faster than (especially for longer types) because it skips type validation. 210 | /// 211 | public static TypeIdDecoded New(string type, bool validateType) => validateType ? New(type) : new TypeIdDecoded(type, UuidGenerator.New()); 212 | 213 | /// 214 | /// Generates new TypeId with the specified type and UUIDv7. 215 | /// 216 | /// Type of the ID. Can be empty. 217 | /// UUIDv7 ID part of the TypeId. 218 | /// New TypeId with the specified type and UUIDv7. 219 | /// Thrown when type is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 220 | /// 221 | /// must be a valid UUIDv7. method generates UUIDv4 which is not valid UUIDv7. 222 | ///

223 | /// This method validates the type. If you are sure that type is valid use to skip type validation. 224 | ///
225 | public static TypeIdDecoded FromUuidV7(string type, Guid uuidV7) 226 | { 227 | if (type.Length > TypeIdConstants.MaxTypeLength) 228 | throw new FormatException($"Type can be at most {TypeIdConstants.MaxTypeLength} characters long."); 229 | var typeError = TypeIdParser.ValidateType(type); 230 | if (typeError is not TypeIdParser.TypeError.None) 231 | throw new FormatException(typeError.ToErrorMessage()); 232 | 233 | return new TypeIdDecoded(type, uuidV7); 234 | } 235 | 236 | /// 237 | /// Generates new TypeId with the specified type and UUIDv7. If is false, type is not validated. 238 | /// 239 | /// Type of the ID. Can be empty. 240 | /// UUIDv7 ID part of the TypeId. 241 | /// If true, type is validated. If false, type is not validated. 242 | /// New TypeId with the specified type and UUIDv7. 243 | /// Thrown when is set to true and is not valid. Type must contain only lowercase ASCII letters and can be at most 63 characters long. 244 | /// 245 | /// must be a valid UUIDv7. method generates UUIDv4 which is not valid UUIDv7. 246 | ///

247 | /// Use this method with set to false when you are sure that is valid. 248 | /// This method is a bit faster than (especially for longer types) because it skips type validation. 249 | ///
250 | public static TypeIdDecoded FromUuidV7(string type, Guid uuidV7, bool validateType) => validateType 251 | ? FromUuidV7(type, uuidV7) 252 | : new TypeIdDecoded(type, uuidV7); 253 | 254 | /// 255 | /// Pre-defined comparers for . 256 | /// 257 | public static class Comparers 258 | { 259 | /// 260 | /// Compares two TypeIdDecoded instances lexicographically. 261 | /// 262 | /// 263 | /// TypeID components are compared in the following order: type, timestamp, and random part of the ID. 264 | /// 265 | public static IComparer Lex => TypeIdDecodedLexComparer.Instance; 266 | 267 | /// 268 | /// Compares two TypeIdDecoded instances by the timestamp part of the ID. 269 | /// 270 | /// 271 | /// TypeID components are compared in the following order: timestamp, random part of the ID, and the type. 272 | /// 273 | public static IComparer Timestamp => TypeIdDecodedTimestampComparer.Instance; 274 | } 275 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeIdDecodedComparers.cs: -------------------------------------------------------------------------------- 1 | using FastIDs.TypeId.Uuid; 2 | 3 | namespace FastIDs.TypeId; 4 | 5 | public class TypeIdDecodedLexComparer : IComparer 6 | { 7 | public static TypeIdDecodedLexComparer Instance { get; } = new(); 8 | 9 | public int Compare(TypeIdDecoded x, TypeIdDecoded y) 10 | { 11 | var typeComparison = string.CompareOrdinal(x.Type, y.Type); 12 | if (typeComparison != 0) 13 | return typeComparison; 14 | 15 | return UuidComparer.Instance.Compare(x.Id, y.Id); 16 | } 17 | } 18 | 19 | public class TypeIdDecodedTimestampComparer : IComparer 20 | { 21 | public static TypeIdDecodedTimestampComparer Instance { get; } = new(); 22 | 23 | public int Compare(TypeIdDecoded x, TypeIdDecoded y) 24 | { 25 | var idComparison = UuidComparer.Instance.Compare(x.Id, y.Id); 26 | if (idComparison != 0) 27 | return idComparison; 28 | 29 | return string.CompareOrdinal(x.Type, y.Type); 30 | } 31 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/TypeIdParser.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Buffers.Binary; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Runtime.Intrinsics; 6 | 7 | namespace FastIDs.TypeId; 8 | 9 | internal static class TypeIdParser 10 | { 11 | private static readonly SearchValues Alphabet = SearchValues.Create("_abcdefghijklmnopqrstuvwxyz"); 12 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 13 | public static void FormatUuidBytes(Span bytes) 14 | { 15 | // Optimized version of: 16 | // (bytes[0], bytes[3]) = (bytes[3], bytes[0]); 17 | // (bytes[1], bytes[2]) = (bytes[2], bytes[1]); 18 | var num = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(bytes[..4])); 19 | num = BinaryPrimitives.ReverseEndianness(num); 20 | Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(bytes[..4]), num); 21 | 22 | // Using permutations here because benchmarks show that they are faster for uint16. 23 | (bytes[4], bytes[5]) = (bytes[5], bytes[4]); 24 | (bytes[6], bytes[7]) = (bytes[7], bytes[6]); 25 | } 26 | 27 | public static TypeError ValidateType(ReadOnlySpan type) 28 | { 29 | if (type.Length == 0) 30 | return TypeError.None; 31 | 32 | if (type[0] == '_') 33 | return TypeError.StartsWithUnderscore; 34 | if (type[^1] == '_') 35 | return TypeError.EndsWithUnderscore; 36 | 37 | // Vectorized version is faster for strings with length >= 8. 38 | const int vectorizedThreshold = 8; 39 | if (Vector128.IsHardwareAccelerated && type.Length >= vectorizedThreshold) 40 | return type.ContainsAnyExcept(Alphabet) ? TypeError.InvalidChar : TypeError.None; 41 | 42 | // Fallback to scalar version for strings with length < 8 or when hardware intrinsics are not available. 43 | foreach (var c in type) 44 | { 45 | var isValidChar = c is >= 'a' and <= 'z' or '_'; 46 | if (!isValidChar) 47 | return TypeError.InvalidChar; 48 | } 49 | 50 | return TypeError.None; 51 | } 52 | 53 | public enum TypeError 54 | { 55 | None, 56 | StartsWithUnderscore, 57 | EndsWithUnderscore, 58 | InvalidChar, 59 | } 60 | 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | public static string ToErrorMessage(this TypeError error) => error switch 63 | { 64 | TypeError.None => "", 65 | TypeError.StartsWithUnderscore => "Type can't start with an underscore.", 66 | TypeError.EndsWithUnderscore => "Type can't end with an underscore.", 67 | TypeError.InvalidChar => "Type must contain only lowercase letters and underscores.", 68 | _ => "Unknown type error." 69 | }; 70 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Uuid/GuidConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | namespace FastIDs.TypeId.Uuid; 4 | 5 | // The UUIDv7 implementation is extracted from https://github.com/mareek/UUIDNext to prevent transient dependency. 6 | // TypeID doesn't require any UUID implementations except UUIDv7. 7 | internal static class GuidConverter 8 | { 9 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 10 | public static Guid CreateGuidFromBigEndianBytes(Span bytes) 11 | { 12 | SetVersion(bytes); 13 | SetVariant(bytes); 14 | return new Guid(bytes, true); 15 | } 16 | 17 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 18 | private static void SetVersion(Span bytes) 19 | { 20 | const byte uuidVersion = 7; 21 | const int versionByteIndex = 6; 22 | //Erase upper 4 bits 23 | bytes[versionByteIndex] &= 0b0000_1111; 24 | //Set 4 upper bits to version 25 | bytes[versionByteIndex] |= uuidVersion << 4; 26 | } 27 | 28 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 29 | private static void SetVariant(Span bytes) 30 | { 31 | const int variantByteIndex = 8; 32 | //Erase upper 2 bits 33 | bytes[variantByteIndex] &= 0b0011_1111; 34 | //Set 2 upper bits to variant 35 | bytes[variantByteIndex] |= 0b1000_0000; 36 | } 37 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Uuid/UuidComparer.cs: -------------------------------------------------------------------------------- 1 | namespace FastIDs.TypeId.Uuid; 2 | 3 | /// 4 | /// Compares two big endian 5 | /// 6 | internal class UuidComparer : IComparer 7 | { 8 | public static UuidComparer Instance { get; } = new(); 9 | 10 | public int Compare(Guid x, Guid y) 11 | { 12 | const int bytesCount = 16; 13 | Span xBytes = stackalloc byte[bytesCount]; 14 | x.TryWriteBytes(xBytes, bigEndian: true, out _); 15 | 16 | Span yBytes = stackalloc byte[bytesCount]; 17 | y.TryWriteBytes(yBytes, bigEndian: true, out _); 18 | 19 | for (var i = 0; i < bytesCount; i++) 20 | { 21 | var compareResult = xBytes[i].CompareTo(yBytes[i]); 22 | if (compareResult != 0) 23 | return compareResult; 24 | } 25 | 26 | return 0; 27 | } 28 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Uuid/UuidDecoder.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Binary; 2 | 3 | namespace FastIDs.TypeId.Uuid; 4 | 5 | // The UUIDv7 implementation is extracted from https://github.com/mareek/UUIDNext to prevent transient dependency. 6 | // TypeID doesn't require any UUID implementations except UUIDv7. 7 | internal static class UuidDecoder 8 | { 9 | public static long DecodeTimestamp(Guid guid) 10 | { 11 | // Allocating 2 bytes more to prepend timestamp data. 12 | Span bytes = stackalloc byte[18]; 13 | guid.TryWriteBytes(bytes[2..], bigEndian: true, out _); 14 | 15 | var timestampBytes = bytes[..8]; 16 | var timestampMs = BinaryPrimitives.ReadInt64BigEndian(timestampBytes); 17 | 18 | return timestampMs; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Core/Uuid/UuidGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Binary; 2 | using System.Runtime.CompilerServices; 3 | using System.Security.Cryptography; 4 | 5 | namespace FastIDs.TypeId.Uuid; 6 | 7 | // The UUIDv7 implementation is extracted from https://github.com/mareek/UUIDNext to prevent transient dependency. 8 | // TypeID doesn't require any UUID implementations except UUIDv7. 9 | 10 | // All timestamps of type `long` in this class are Unix milliseconds unless stated otherwise. 11 | internal sealed class UuidGenerator 12 | { 13 | private const int SequenceBitSize = 7; 14 | private const int SequenceMaxValue = (1 << SequenceBitSize) - 1; 15 | 16 | private long _lastUsedTimestamp; 17 | private long _timestampOffset; 18 | private ushort _monotonicSequence; 19 | 20 | public Guid New() 21 | { 22 | // Allocating 2 bytes more to prepend timestamp data. 23 | Span buffer = stackalloc byte[18]; 24 | 25 | // Offset bytes that are used in ID. 26 | var idBytes = buffer[2..]; 27 | 28 | var timestamp = GetCurrentUnixMilliseconds(); 29 | SetSequence(idBytes[6..8], ref timestamp); 30 | SetTimestamp(buffer[..8], timestamp); // Using full buffer because we need to account for two zero-bytes in front. 31 | RandomNumberGenerator.Fill(idBytes[8..]); 32 | 33 | return GuidConverter.CreateGuidFromBigEndianBytes(idBytes); 34 | } 35 | 36 | // The implementation copied from DateTimeOffset.ToUnixTimeMilliseconds() 37 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 38 | private static long GetCurrentUnixMilliseconds() => DateTime.UtcNow.Ticks / 10000L - 62135596800000L; 39 | 40 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 41 | private static void SetTimestamp(Span bytes, long unixMs) 42 | { 43 | BinaryPrimitives.TryWriteInt64BigEndian(bytes, unixMs); 44 | } 45 | 46 | private void SetSequence(Span bytes, ref long timestamp) 47 | { 48 | ushort sequence; 49 | var originalTimestamp = timestamp; 50 | 51 | lock (this) 52 | { 53 | sequence = GetSequenceNumber(ref timestamp); 54 | if (sequence > SequenceMaxValue) 55 | { 56 | // if the sequence is greater than the max value, we take advantage 57 | // of the anti-rewind mechanism to simulate a slight change in clock time 58 | timestamp = originalTimestamp + 1; 59 | sequence = GetSequenceNumber(ref timestamp); 60 | } 61 | } 62 | 63 | BinaryPrimitives.TryWriteUInt16BigEndian(bytes, sequence); 64 | } 65 | 66 | private ushort GetSequenceNumber(ref long timestamp) 67 | { 68 | EnsureTimestampNeverMoveBackward(ref timestamp); 69 | 70 | if (timestamp == _lastUsedTimestamp) 71 | { 72 | _monotonicSequence += 1; 73 | } 74 | else 75 | { 76 | _lastUsedTimestamp = timestamp; 77 | _monotonicSequence = GetSequenceSeed(); 78 | } 79 | 80 | return _monotonicSequence; 81 | } 82 | 83 | private void EnsureTimestampNeverMoveBackward(ref long timestamp) 84 | { 85 | var lastUsedTs = _lastUsedTimestamp; 86 | if (_timestampOffset > 0 && timestamp > lastUsedTs) 87 | { 88 | // reset the offset to reduce the drift with the actual time when possible 89 | _timestampOffset = 0; 90 | return; 91 | } 92 | 93 | var offsetTimestamp = timestamp + _timestampOffset; 94 | if (offsetTimestamp < lastUsedTs) 95 | { 96 | // if the computer clock has moved backward since the last generated UUID, 97 | // we add an offset to ensure the timestamp always move forward (See RFC Section 6.2) 98 | _timestampOffset = lastUsedTs - timestamp; 99 | timestamp = lastUsedTs; 100 | return; 101 | } 102 | 103 | // Happy path 104 | timestamp = offsetTimestamp; 105 | } 106 | 107 | 108 | private static ushort GetSequenceSeed() 109 | { 110 | // following section 6.2 on "Fixed-Length Dedicated Counter Seeding", the initial value of the sequence is randomized 111 | Span buffer = stackalloc byte[2]; 112 | RandomNumberGenerator.Fill(buffer); 113 | // Setting the highest bit to 0 mitigate the risk of a sequence overflow (see section 6.2) 114 | buffer[0] &= 0b0000_0111; 115 | return BinaryPrimitives.ReadUInt16BigEndian(buffer); 116 | } 117 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeId.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | Nullable 7 | 8 | false 9 | 10 | TypeId.Tests 11 | 12 | TypeIdTests 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/ComparisonTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FastIDs.TypeId.Uuid; 4 | using FluentAssertions; 5 | 6 | namespace TypeIdTests.TypeIdTests; 7 | 8 | [TestFixture] 9 | public class ComparisonTests 10 | { 11 | [Test] 12 | public void TypeId_DifferentPrefix_PrefixesComparedLexicographically() 13 | { 14 | var first = TypeId.New("bbb").Encode(); 15 | var second = TypeId.New("aaa").Encode(); 16 | 17 | first.Should().BeGreaterThan(second); 18 | } 19 | 20 | [Test] 21 | public void TypeId_SamePrefix_OlderIdIsLessThanNewer() 22 | { 23 | var first = TypeId.New("aaa").Encode(); 24 | var second = TypeId.New("aaa").Encode(); 25 | 26 | first.Should().BeLessThan(second); 27 | } 28 | 29 | [Test] 30 | public void UuidV7_DifferentTimestamps_OlderIdIsLessThanNewer() 31 | { 32 | var first = TypeId.New("aaa"); 33 | var second = TypeId.New("aaa"); 34 | 35 | var result = UuidComparer.Instance.Compare(first.Id, second.Id); 36 | result.Should().BeLessThan(0); 37 | } 38 | 39 | [Test] 40 | public void UuidV7_DifferentTimestamps_NewerIsGreaterThanOlder() 41 | { 42 | var first = TypeId.New("aaa"); 43 | var second = TypeId.New("aaa"); 44 | 45 | var result = UuidComparer.Instance.Compare(second.Id, first.Id); 46 | result.Should().BeGreaterThan(0); 47 | } 48 | 49 | [Test] 50 | public void UuidV7_SameTimestamps_IdsAreEqual() 51 | { 52 | var first = TypeId.New("aaa"); 53 | 54 | var result = UuidComparer.Instance.Compare(first.Id, first.Id); 55 | result.Should().Be(0); 56 | } 57 | 58 | [TestCase("bbb", "aaa", 1)] 59 | [TestCase("aaa", "bbb", -1)] 60 | public void TypeIdDecodedLexComparer_DifferentPrefix_TypesComparedLexicographically(string firstPrefix, string secondPrefix, int expected) 61 | { 62 | var first = TypeIdDecoded.New(firstPrefix); 63 | var second = TypeIdDecoded.New(secondPrefix); 64 | 65 | var result = TypeIdDecoded.Comparers.Lex.Compare(first, second); 66 | result.Should().Be(expected); 67 | } 68 | 69 | [Test] 70 | public void TypeIdDecodedLexComparer_SamePrefix_OlderIdIsLessThanNewer() 71 | { 72 | var first = TypeIdDecoded.New("aaa"); 73 | var second = TypeIdDecoded.New("aaa"); 74 | 75 | var result = TypeIdDecoded.Comparers.Lex.Compare(first, second); 76 | result.Should().BeLessThan(0); 77 | } 78 | 79 | [Test] 80 | public void TypeIdDecodedTimestampComparer_DifferentTimestamps_OlderIdIsLessThanNewer() 81 | { 82 | var first = TypeIdDecoded.New("aaa"); 83 | var second = TypeIdDecoded.New("aaa"); 84 | 85 | var result = TypeIdDecoded.Comparers.Timestamp.Compare(first, second); 86 | result.Should().BeLessThan(0); 87 | } 88 | 89 | [TestCase("aaa", "bbb", -1)] 90 | [TestCase("bbb", "aaa", 1)] 91 | [TestCase("aaa", "aaa", 0)] 92 | public void TypeIdDecodedTimestampComparer_SameUuidv7_TypeIsComparedLexicographically(string firstPrefix, string secondPrefix, int expected) 93 | { 94 | var first = TypeIdDecoded.FromUuidV7(firstPrefix, Guid.Parse("01953e9c-1f53-771b-a158-cb2c3c59bed4")); 95 | var second = TypeIdDecoded.FromUuidV7(secondPrefix, Guid.Parse("01953e9c-1f53-771b-a158-cb2c3c59bed4")); 96 | 97 | var result = TypeIdDecoded.Comparers.Timestamp.Compare(first, second); 98 | result.Should().Be(expected); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/FormattingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Text.Unicode; 5 | using FastIDs.TypeId; 6 | using FluentAssertions; 7 | 8 | namespace TypeIdTests.TypeIdTests; 9 | 10 | [TestFixture] 11 | public class FormattingTests 12 | { 13 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 14 | public void Encoded_Suffix(string typeIdStr, Guid expectedGuid, string expectedType) 15 | { 16 | var typeId = TypeId.Parse(typeIdStr); 17 | 18 | var suffix = typeId.Suffix.ToString(); 19 | var expectedSuffix = typeIdStr.Split('_')[^1]; 20 | 21 | suffix.Should().Be(expectedSuffix); 22 | } 23 | 24 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 25 | public void Encoded_Type(string typeIdStr, Guid expectedGuid, string expectedType) 26 | { 27 | var typeId = TypeId.Parse(typeIdStr); 28 | 29 | var type = typeId.Type.ToString(); 30 | 31 | type.Should().Be(expectedType); 32 | } 33 | 34 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 35 | public void Encoded_ToString(string typeIdStr, Guid expectedGuid, string expectedType) 36 | { 37 | var typeId = TypeId.Parse(typeIdStr); 38 | 39 | typeId.ToString().Should().Be(typeIdStr); 40 | } 41 | 42 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 43 | public void Encoded_ToStringFormat(string typeIdStr, Guid expectedGuid, string expectedType) 44 | { 45 | var typeId = TypeId.Parse(typeIdStr); 46 | 47 | typeId.ToString("", null).Should().Be(typeIdStr); 48 | } 49 | 50 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 51 | public void Encoded_TryFormat(string typeIdStr, Guid expectedGuid, string expectedType) 52 | { 53 | var typeId = TypeId.Parse(typeIdStr); 54 | 55 | Span formattedTypeId = stackalloc char[typeIdStr.Length + 10]; 56 | typeId.TryFormat(formattedTypeId, out var charsWritten, "", null).Should().BeTrue(); 57 | 58 | formattedTypeId[..charsWritten].ToString().Should().Be(typeIdStr); 59 | } 60 | 61 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 62 | public void Encoded_TryFormatUtf8(string typeIdStr, Guid expectedGuid, string expectedType) 63 | { 64 | var typeId = TypeId.Parse(typeIdStr); 65 | 66 | Span formattedTypeId = stackalloc byte[typeIdStr.Length + 10]; 67 | typeId.TryFormat(formattedTypeId, out var bytesWritten, "", null).Should().BeTrue(); 68 | 69 | Encoding.UTF8.GetString(formattedTypeId[..bytesWritten]).Should().Be(typeIdStr); 70 | } 71 | 72 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 73 | public void Decoded_GetSuffix(string typeIdStr, Guid expectedGuid, string expectedType) 74 | { 75 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 76 | 77 | var expectedSuffix = typeIdStr.Split('_')[^1]; 78 | 79 | decoded.GetSuffix().Should().Be(expectedSuffix); 80 | } 81 | 82 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 83 | public void Decoded_GetSuffixSpan(string typeIdStr, Guid expectedGuid, string expectedType) 84 | { 85 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 86 | 87 | var expectedSuffix = typeIdStr.Split('_')[^1]; 88 | 89 | Span suffix = stackalloc char[expectedSuffix.Length + 10]; 90 | var charsWritten = decoded.GetSuffix(suffix); 91 | 92 | charsWritten.Should().Be(expectedSuffix.Length); 93 | suffix[..charsWritten].ToString().Should().Be(expectedSuffix); 94 | } 95 | 96 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 97 | public void Decoded_GetSuffixUtf8Span(string typeIdStr, Guid expectedGuid, string expectedType) 98 | { 99 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 100 | 101 | var expectedSuffix = typeIdStr.Split('_')[^1]; 102 | 103 | Span utf8Suffix = stackalloc byte[expectedSuffix.Length + 10]; 104 | var bytesWritten = decoded.GetSuffix(utf8Suffix); 105 | 106 | bytesWritten.Should().Be(expectedSuffix.Length); 107 | var suffix = Encoding.UTF8.GetString(utf8Suffix[..bytesWritten]); 108 | suffix.Should().Be(expectedSuffix); 109 | } 110 | 111 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 112 | public void Decoded_ToString(string typeIdStr, Guid expectedGuid, string expectedType) 113 | { 114 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 115 | 116 | decoded.ToString().Should().Be(typeIdStr); 117 | } 118 | 119 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 120 | public void Decoded_ToStringFormat(string typeIdStr, Guid expectedGuid, string expectedType) 121 | { 122 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 123 | 124 | decoded.ToString("", null).Should().Be(typeIdStr); 125 | } 126 | 127 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 128 | public void Decoded_TryFormat(string typeIdStr, Guid expectedGuid, string expectedType) 129 | { 130 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 131 | 132 | Span formattedTypeId = stackalloc char[typeIdStr.Length + 10]; 133 | decoded.TryFormat(formattedTypeId, out var charsWritten, "", null).Should().BeTrue(); 134 | 135 | formattedTypeId[..charsWritten].ToString().Should().Be(typeIdStr); 136 | } 137 | 138 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 139 | public void Decoded_TryFormatUtf8(string typeIdStr, Guid expectedGuid, string expectedType) 140 | { 141 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 142 | 143 | Span formattedTypeId = stackalloc byte[typeIdStr.Length + 10]; 144 | decoded.TryFormat(formattedTypeId, out var bytesWritten, "", null).Should().BeTrue(); 145 | 146 | Encoding.UTF8.GetString(formattedTypeId[..bytesWritten]).Should().Be(typeIdStr); 147 | } 148 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/GenerationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FluentAssertions; 4 | 5 | namespace TypeIdTests.TypeIdTests; 6 | 7 | [TestFixture] 8 | public class GenerationTests 9 | { 10 | [TestCaseSource(typeof(TestCases), nameof(TestCases.WithPrefix))] 11 | public void FromUuidV7_TypeIdCreated(string typeIdStr, Guid uuidV7, string prefix) 12 | { 13 | var typeId = TypeId.FromUuidV7(prefix, uuidV7); 14 | 15 | typeId.ToString().Should().Be(typeIdStr); 16 | } 17 | 18 | [TestCaseSource(nameof(ValidTypes))] 19 | public void New_WithType_TypeIdCreated(string type) 20 | { 21 | var typeId = TypeId.New(type); 22 | 23 | typeId.Type.Should().Be(type); 24 | typeId.Id.Should().NotBeEmpty(); 25 | } 26 | 27 | [TestCaseSource(nameof(InvalidTypes))] 28 | public void New_IncorrectType_FormatExceptionThrown(string type) 29 | { 30 | var act = () => TypeId.New(type); 31 | act.Should().Throw(); 32 | } 33 | 34 | [TestCaseSource(nameof(InvalidTypes))] 35 | public void FromUuidV7_IncorrectType_FormatExceptionThrown(string type) 36 | { 37 | var act = () => TypeId.FromUuidV7(type, Guid.NewGuid()); 38 | act.Should().Throw(); 39 | } 40 | 41 | private static TestCaseData[] ValidTypes => 42 | [ 43 | new("prefix") { TestName = "Lowercase letters type" }, 44 | new("pre_fix") { TestName = "Lowercase letters with underscore type" }, 45 | new("") { TestName = "Empty type" }, 46 | ]; 47 | 48 | private static TestCaseData[] InvalidTypes => 49 | [ 50 | new("PREFIX") { TestName = "Type can't have any uppercase letters" }, 51 | new("pre.fix") { TestName = "Type can't have any special characters" }, 52 | new("pre fix") { TestName = "Type can't have any spaces" }, 53 | new("préfix") { TestName = "Type can't have any non-ASCII characters" }, 54 | new("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl") { TestName = "Type can't have have more than 63 characters" }, 55 | new("_prefix") { TestName = "The prefix can't start with an underscore" }, 56 | new("prefix_") { TestName = "The prefix can't end with an underscore" }, 57 | ]; 58 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/IdConvertTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace TypeIdTests.TypeIdTests; 7 | 8 | [TestFixture] 9 | public class IdConvertTests 10 | { 11 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 12 | public void Decode_ValidIds(string typeIdStr, Guid expectedGuid, string expectedPrefix) 13 | { 14 | var typeId = TypeId.Parse(typeIdStr); 15 | var decoded = typeId.Decode(); 16 | 17 | decoded.Type.Should().Be(expectedPrefix); 18 | decoded.Id.Should().Be(expectedGuid); 19 | } 20 | 21 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 22 | public void Encode_ValidIds(string typeIdStr, Guid expectedGuid, string expectedPrefix) 23 | { 24 | var decoded = TypeId.FromUuidV7(expectedPrefix, expectedGuid); 25 | 26 | var typeId = decoded.Encode(); 27 | 28 | typeId.ToString().Should().Be(typeIdStr); 29 | } 30 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/ParsingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FluentAssertions; 4 | 5 | namespace TypeIdTests.TypeIdTests; 6 | 7 | [TestFixture] 8 | public class ParsingTests 9 | { 10 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 11 | public void Parse_ValidIds_Parsed(string typeIdStr, Guid expectedGuid, string expectedPrefix) 12 | { 13 | var typeId = TypeId.Parse(typeIdStr); 14 | 15 | typeId.ToString().Should().Be(typeIdStr); 16 | typeId.Type.ToString().Should().Be(expectedPrefix); 17 | } 18 | 19 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 20 | public void TryParse_ValidIds_Parsed(string typeIdStr, Guid expectedGuid, string expectedPrefix) 21 | { 22 | var canParse = TypeId.TryParse(typeIdStr, out var typeId); 23 | 24 | canParse.Should().BeTrue(); 25 | typeId.ToString().Should().Be(typeIdStr); 26 | typeId.Type.ToString().Should().Be(expectedPrefix); 27 | } 28 | 29 | [TestCaseSource(typeof(TestCases), nameof(TestCases.InvalidIds))] 30 | public void Parse_InvalidId_ThrowsFormatException(string typeIdStr) 31 | { 32 | FluentActions.Invoking(() => TypeId.Parse(typeIdStr)) 33 | .Should().Throw(); 34 | } 35 | 36 | [TestCaseSource(typeof(TestCases), nameof(TestCases.InvalidIds))] 37 | public void TryParse_InvalidId_ReturnsFalse(string typeIdStr) 38 | { 39 | var canParse = TypeId.TryParse(typeIdStr, out var typeId); 40 | 41 | canParse.Should().BeFalse(); 42 | typeId.Should().Be((TypeId)default); 43 | } 44 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/TestCases.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace TypeIdTests.TypeIdTests; 5 | 6 | public static class TestCases 7 | { 8 | public static TestCaseData[] NoPrefix => 9 | [ 10 | new("00000000000000000000000000", new Guid("00000000-0000-0000-0000-000000000000"), ""), 11 | new("00000000000000000000000001", new Guid("00000000-0000-0000-0000-000000000001"), ""), 12 | new("0000000000000000000000000a", new Guid("00000000-0000-0000-0000-00000000000a"), ""), 13 | new("00000000000000000000000010", new Guid("00000000-0000-0000-0000-000000000020"), ""), 14 | new("7zzzzzzzzzzzzzzzzzzzzzzzzz", new Guid("ffffffff-ffff-ffff-ffff-ffffffffffff"), ""), 15 | new("0000000000000000000000000g", new Guid("00000000-0000-0000-0000-000000000010"), ""), 16 | new("00000000000000000000000010", new Guid("00000000-0000-0000-0000-000000000020"), ""), 17 | new("01h455vb4pex5vsknk084sn02q", new Guid("01890a5d-ac96-774b-bcce-b302099a8057"), ""), 18 | ]; 19 | 20 | public static TestCaseData[] WithPrefix => 21 | [ 22 | new("zeroid_00000000000000000000000000", new Guid("00000000-0000-0000-0000-000000000000"), "zeroid"), 23 | new("maxid_7zzzzzzzzzzzzzzzzzzzzzzzzz", new Guid("ffffffff-ffff-ffff-ffff-ffffffffffff"), "maxid"), 24 | new("prefix_0123456789abcdefghjkmnpqrs", new Guid("0110c853-1d09-52d8-d73e-1194e95b5f19"), "prefix"), 25 | new("type_01h455vb4pex5vsknk084sn02q", new Guid("01890a5d-ac96-774b-bcce-b302099a8057"), "type"), 26 | new($"{new string('a', 63)}_0123456789abcdefghjkmnpqrs", new Guid("0110c853-1d09-52d8-d73e-1194e95b5f19"), new string('a', 63)), 27 | new("pre_fix_00000000000000000000000000", new Guid("00000000-0000-0000-0000-000000000000"), "pre_fix"), 28 | ]; 29 | 30 | public static TestCaseData[] ValidIds => WithPrefix.Concat(NoPrefix).ToArray(); 31 | 32 | public static TestCaseData[] InvalidIds => 33 | [ 34 | new("PREFIX_00000000000000000000000000") { TestName = "The prefix should be lowercase with no uppercase letters" }, 35 | new("12345_00000000000000000000000000") { TestName = "The prefix can't have numbers, it needs to be alphabetic" }, 36 | new("pre.fix_00000000000000000000000000") { TestName = "The prefix can't have symbols (period), it needs to be alphabetic" }, 37 | new("préfix_00000000000000000000000000") { TestName = "The prefix can only have ascii letters" }, 38 | new(" prefix_00000000000000000000000000") { TestName = "The prefix can't have any spaces" }, 39 | new("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl_00000000000000000000000000") { TestName = "The prefix can't be 64 characters, it needs to be 63 characters or less" }, 40 | new("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij0_00000000000000000000000000") { TestName = "The long prefix can't have numbers" }, 41 | new("_00000000000000000000000000") { TestName = "If the prefix is empty, the separator should not be there" }, 42 | new("_") { TestName = "A separator by itself should not be treated as the empty string" }, 43 | new("prefix_1234567890123456789012345") { TestName = "The suffix can't be 25 characters, it needs to be exactly 26 characters" }, 44 | new("prefix_123456789012345678901234567") { TestName = "The suffix can't be 27 characters, it needs to be exactly 26 characters" }, 45 | new("prefix_1234567890123456789012345 ") { TestName = "The suffix can't have any spaces" }, 46 | new("prefix_0123456789ABCDEFGHJKMNPQRS") { TestName = "The suffix should be lowercase with no uppercase letters" }, 47 | new("prefix_prefix_123456789-123456789-123456") { TestName = "The suffix should be lowercase with no uppercase letters" }, 48 | new("prefix_ooooooiiiiiiuuuuuuulllllll") { TestName = "The suffix should only have letters from the spec's alphabet" }, 49 | new("prefix_i23456789ol23456789oi23456") { TestName = "The suffix should not have any ambiguous characters from the crockford encoding" }, 50 | new("prefix_123456789-0123456789-0123456") { TestName = "The suffix can't ignore hyphens as in the crockford encoding" }, 51 | new("prefix_8zzzzzzzzzzzzzzzzzzzzzzzzz") { TestName = "The suffix should encode at most 128-bits" }, 52 | new("_prefix_00000000000000000000000000") { TestName = "The prefix can't start with an underscore" }, 53 | new("prefix__00000000000000000000000000") { TestName = "The prefix can't end with an underscore" }, 54 | ]; 55 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/TimestampTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FluentAssertions; 4 | 5 | namespace TypeIdTests.TypeIdTests; 6 | 7 | [TestFixture] 8 | public class TimestampTests 9 | { 10 | [Test] 11 | public void GetTimestamp_NewlyGenerated() 12 | { 13 | var typeId = TypeId.New("", false); 14 | 15 | var timestamp = typeId.GetTimestamp(); 16 | 17 | timestamp.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMilliseconds(100)); 18 | } 19 | 20 | [Test] 21 | public void GetTimestamp_ExistingId() 22 | { 23 | var typeId = TypeId.FromUuidV7("", new Guid("01896af3-a83a-7155-bf7e-fff6e73fe09d"), false); 24 | 25 | var timestamp = typeId.GetTimestamp(); 26 | var expectedTimestamp = new DateTimeOffset(2023, 07, 18, 21, 41, 40, 538, TimeSpan.Zero); 27 | 28 | timestamp.Should().Be(expectedTimestamp); 29 | } 30 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/TypeIdTests/TypeMatchingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FastIDs.TypeId; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace TypeIdTests.TypeIdTests; 7 | 8 | [TestFixture] 9 | public class TypeMatchingTests 10 | { 11 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 12 | public void Encoded_HasType_Valid(string typeIdStr, Guid expectedGuid, string expectedType) 13 | { 14 | var typeId = TypeId.Parse(typeIdStr); 15 | 16 | typeId.HasType(expectedType).Should().BeTrue(); 17 | } 18 | 19 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 20 | public void Encoded_HasType_WrongType(string typeIdStr, Guid expectedGuid, string expectedType) 21 | { 22 | var typeId = TypeId.Parse(typeIdStr); 23 | 24 | typeId.HasType(expectedType + '#').Should().BeFalse(); 25 | } 26 | 27 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 28 | public void Encoded_HasTypeSpan_Valid(string typeIdStr, Guid expectedGuid, string expectedType) 29 | { 30 | var typeId = TypeId.Parse(typeIdStr); 31 | 32 | typeId.HasType(expectedType.AsSpan()).Should().BeTrue(); 33 | } 34 | 35 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 36 | public void Decoded_HasType_Valid(string typeIdStr, Guid expectedGuid, string expectedType) 37 | { 38 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 39 | 40 | decoded.HasType(expectedType).Should().BeTrue(); 41 | } 42 | 43 | [TestCaseSource(typeof(TestCases), nameof(TestCases.ValidIds))] 44 | public void Decoded_HasTypeSpan_Valid(string typeIdStr, Guid expectedGuid, string expectedType) 45 | { 46 | var decoded = TypeId.FromUuidV7(expectedType, expectedGuid); 47 | 48 | decoded.HasType(expectedType.AsSpan()).Should().BeTrue(); 49 | } 50 | } -------------------------------------------------------------------------------- /src/FastIDs.TypeId/TypeId.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | // Global using directives 2 | 3 | global using NUnit.Framework; --------------------------------------------------------------------------------