diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml deleted file mode 100644 index 1230149..0000000 --- a/.github/dependabot.yaml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 41d6c67..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,61 +0,0 @@ -on: - push: {} - pull_request: {} - workflow_dispatch: {} - -jobs: - pinact: - runs-on: ubuntu-latest - name: Check Github Actions Pins - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: cachix/install-nix-action@c134e4c9e34bac6cab09cf239815f9339aaaf84e # v31.5.1 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: cachix/cachix-action@18cf96c7c98e048e10a83abd92116114cd8504be # v14 - with: - name: jcollie - signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" - authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - name: pinact check - run: nix develop -c pinact run --check - - zig-fmt: - runs-on: ubuntu-latest - name: Check Zig formatting - needs: - - pinact - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5 - with: - version: master - - run: zig fmt --check . - - test_0_14: - runs-on: ubuntu-latest - name: Test with Zig 0.14 - needs: - - pinact - - zig-fmt - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5 - with: - version: 0.14.1 - - run: zig build test - - test_0_15: - runs-on: ubuntu-latest - name: Test with Zig 0.15 - needs: - - pinact - - zig-fmt - - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: mlugg/setup-zig@8d6198c65fb0feaa111df26e6b467fea8345e46f # v2.0.5 - with: - version: master - - run: zig build test diff --git a/.gitignore b/.gitignore index bc6a357..0e36d13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ -/.zig-cache -/zig-out +# SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +# SPDX-License-Identifier: MIT +.zig-cache/ +zig-out/ +zig-pkg/ +result* diff --git a/.tangled/workflows/ci.yml b/.tangled/workflows/ci.yml new file mode 100644 index 0000000..175383e --- /dev/null +++ b/.tangled/workflows/ci.yml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +# SPDX-License-Identifier: MIT + +when: + - event: + - push + branch: + - main + - event: + - manual + +engine: nixery + +dependencies: + nixpkgs: + - curl + - glibc + + github:NixOS/nixpkgs/nixpkgs-unstable: + - zig_0_16 + +environment: + TERM: dumb + NO_COLOR: "true" + +steps: + - name: Check formatting + command: zig fmt --check . + + - name: Run tests + command: zig build test + + - name: Build API docs + command: zig build docs + + - name: Deploy API docs to Wisp + command: | + curl -s -o ./wispcli https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux + chmod a+x ./wispcli + ./wispcli \ + "$WISP_HANDLE" \ + --path ./zig-out/docs \ + --site "$WISP_SITE" \ + --password "$WISP_APP_PASSWORD" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2e486f2..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Jeffrey C. Ollie - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/REUSE.toml b/REUSE.toml new file mode 100644 index 0000000..a3010ce --- /dev/null +++ b/REUSE.toml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: © 2024 Jeffrey C. Ollie +# SPDX-License-Identifier: GPL-3.0-or-later + +version = 1 + +[[annotations]] +path = "flake.lock" +SPDX-FileCopyrightText = "NONE" +SPDX-License-Identifier = "CC0-1.0" diff --git a/build.zig b/build.zig index b9a133e..6fea3dc 100644 --- a/build.zig +++ b/build.zig @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + const std = @import("std"); pub fn build(b: *std.Build) void { @@ -18,4 +21,18 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Run tests"); test_step.dependOn(&run_mod_tests.step); + + const lib = b.addLibrary(.{ + .name = "uuid", + .root_module = mod, + }); + + const install_docs = b.addInstallDirectory(.{ + .source_dir = lib.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + + const docs_step = b.step("docs", "Build the API docs"); + docs_step.dependOn(&install_docs.step); } diff --git a/build.zig.zon b/build.zig.zon index dc40e79..c10c797 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,81 +1,17 @@ +// SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + .{ - // This is the default name used by packages depending on this one. For - // example, when a user runs `zig fetch --save `, this field is used - // as the key in the `dependencies` table. Although the user can choose a - // different name, most users will stick with this provided value. - // - // It is redundant to include "zig" in this name because it is already - // within the Zig package namespace. - .name = .zig_uuid, - // This is a [Semantic Version](https://semver.org/). - // In a future version of Zig it will be used for package deduplication. + .name = .uuid, .version = "0.0.0", - // Together with name, this represents a globally unique package - // identifier. This field is generated by the Zig toolchain when the - // package is first created, and then *never changes*. This allows - // unambiguous detection of one package being an updated version of - // another. - // - // When forking a Zig project, this id should be regenerated (delete the - // field and run `zig build`) if the upstream project is still maintained. - // Otherwise, the fork is *hostile*, attempting to take control over the - // original project's identity. Thus it is recommended to leave the comment - // on the following line intact, so that it shows up in code reviews that - // modify the field. - .fingerprint = 0x1915b3454b88a934, // Changing this has security and trust implications. - // Tracks the earliest Zig version that the package considers to be a - // supported use case. - .minimum_zig_version = "0.15.0-dev.1474+3ea015db9", - // This field is optional. - // Each dependency must either provide a `url` and `hash`, or a `path`. - // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. - // Once all dependencies are fetched, `zig build` no longer requires - // internet connectivity. - .dependencies = .{ - // See `zig fetch --save ` for a command-line interface for adding dependencies. - //.example = .{ - // // When updating this field to a new URL, be sure to delete the corresponding - // // `hash`, otherwise you are communicating that you expect to find the old hash at - // // the new URL. If the contents of a URL change this will result in a hash mismatch - // // which will prevent zig from using it. - // .url = "https://example.com/foo.tar.gz", - // - // // This is computed from the file contents of the directory of files that is - // // obtained after fetching `url` and applying the inclusion rules given by - // // `paths`. - // // - // // This field is the source of truth; packages do not come from a `url`; they - // // come from a `hash`. `url` is just one of many possible mirrors for how to - // // obtain a package matching this `hash`. - // // - // // Uses the [multihash](https://multiformats.io/multihash/) format. - // .hash = "...", - // - // // When this is provided, the package is found in a directory relative to the - // // build root. In this case the package's hash is irrelevant and therefore not - // // computed. This field and `url` are mutually exclusive. - // .path = "foo", - // - // // When this is set to `true`, a package is declared to be lazily - // // fetched. This makes the dependency only get fetched if it is - // // actually used. - // .lazy = false, - //}, - }, - // Specifies the set of files and directories that are included in this package. - // Only files and directories listed here are included in the `hash` that - // is computed for this package. Only files listed here will remain on disk - // when using the zig package manager. As a rule of thumb, one should list - // files required for compilation plus any license(s). - // Paths are relative to the build root. Use the empty string (`""`) to refer to - // the build root itself. - // A directory listed here means that all files within, recursively, are included. + .fingerprint = 0xd17f50a64fdbdc53, // Changing this has security and trust implications. + .minimum_zig_version = "0.16.0", + .dependencies = .{}, .paths = .{ "build.zig", "build.zig.zon", "src", - // For example... - //"LICENSE", + "LICENSES", //"README.md", }, } diff --git a/flake.lock b/flake.lock index 49106db..452d045 100644 --- a/flake.lock +++ b/flake.lock @@ -1,46 +1,12 @@ { "nodes": { - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1754725699, - "narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { @@ -51,51 +17,7 @@ }, "root": { "inputs": { - "flake-compat": "flake-compat", - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "zig": "zig" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "zig": { - "inputs": { - "flake-compat": [ - "flake-compat" - ], - "flake-utils": [ - "flake-utils" - ], - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1754914485, - "narHash": "sha256-/oGhrnR5ZOYGpwsUOLcnIu4z4hkh6x/NkB+PSRMjnuQ=", - "owner": "mitchellh", - "repo": "zig-overlay", - "rev": "ffcc8c880dffb4a8198f2a2b07d0d4d65ba93f53", - "type": "github" - }, - "original": { - "owner": "mitchellh", - "repo": "zig-overlay", - "type": "github" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 9314ff7..8824555 100644 --- a/flake.nix +++ b/flake.nix @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +# SPDX-License-Identifier: MIT + { description = "zig-uuid"; @@ -5,54 +8,37 @@ nixpkgs = { url = "nixpkgs/nixos-unstable"; }; - flake-utils = { - url = "github:numtide/flake-utils"; - }; - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; - zig = { - url = "github:mitchellh/zig-overlay"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; - flake-compat.follows = "flake-compat"; - }; - }; }; - outputs = { - self, - nixpkgs, - flake-utils, - zig, - ... - }: let - in - flake-utils.lib.eachDefaultSystem ( - system: let - pkgs = import nixpkgs { + outputs = + { + nixpkgs, + ... + }: + let + lib = nixpkgs.lib; + platforms = [ + "aarch64-darwin" + "aarch64-linux" + "x86_64-linux" + ]; + makePackages = + system: + import nixpkgs { inherit system; + overlays = [ ]; }; - in { - devShells = { - zig_0_14 = pkgs.mkShell { - name = "zig-uuid-0.14"; - nativeBuildInputs = [ - pkgs.zig_0_14 - pkgs.pinact - ]; - }; - zig_0_15 = pkgs.mkShell { - name = "zig-uuid-0.15"; - nativeBuildInputs = [ - zig.packages.${system}.master - pkgs.pinact - ]; - }; - default = self.devShells.${system}.zig_0_15; + forAllSystems = (function: lib.genAttrs platforms (system: function (makePackages system))); + in + { + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.zig_0_16 + pkgs.pinact + pkgs.reuse + ]; }; - } - ); + }); + }; } diff --git a/src/root.zig b/src/root.zig index 3f28118..c302ad2 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,5 +1,6 @@ -//! © 2025 Jeffrey C. Ollie -//! +// SPDX-FileCopyrightText: © 2025 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + const std = @import("std"); const assert = std.debug.assert; @@ -92,8 +93,9 @@ pub const UUID = packed union { const uuid_epoch_offset = 0x01b21dd213814000; /// Return the current UUID epoch timestamp. - pub fn now() v1v6Time { - return .{ .raw = @intCast(@divFloor(std.time.nanoTimestamp(), 100) + uuid_epoch_offset) }; + pub fn now(io: std.Io) v1v6Time { + const ts: std.Io.Timestamp = .now(io, .real); + return .{ .raw = @intCast(@divFloor(ts.toNanoseconds(), 100) + uuid_epoch_offset) }; } /// Return the UUID epoch timestamp that corresponds to the given Unix @@ -126,9 +128,15 @@ pub const UUID = packed union { /// The time in 100-nanosecond intervals since the UUID epoch, which /// is 1582-10-15 00:00:00. If this is left null the current time /// will be used. - time: ?v1v6Time = null, - clock_seq: ?u14 = null, - node: ?u48 = null, + time: v1v6Time, + clock_seq: union(enum) { + raw: u14, + random: std.Random, + }, + node: union(enum) { + raw: u48, + random: std.Random, + }, }, v2, v3: struct { @@ -144,7 +152,9 @@ pub const UUID = packed union { return @bitCast(std.mem.readInt(u128, self.hash, .big)); } }, - v4, + v4: struct { + rng: std.Random, + }, v5: struct { hash: *const [std.crypto.hash.Sha1.digest_length]u8, @@ -161,18 +171,27 @@ pub const UUID = packed union { }, v6: struct { /// The time in 100-nanosecond intervals since the UUID epoch, which - /// is 1582-10-15 00:00:00. If this is left null the current time - /// will be used. - time: ?v1v6Time = null, - clock_seq: ?u14 = null, - node: ?u48 = null, + /// is 1582-10-15 00:00:00. + time: v1v6Time, + clock_seq: union(enum) { + raw: u14, + random: std.Random, + }, + node: union(enum) { + raw: u48, + random: std.Random, + }, }, v7: struct { - unix_ts_ms: ?u48 = null, + unix_ts_ms: union(enum) { + raw: u48, + timestamp: std.Io.Timestamp, + }, + rng: std.Random, }, v8: packed union { custom: u122, - part: packed struct(u122) { + parts: packed struct(u122) { a: u48, b: u12, c: u62, @@ -297,10 +316,15 @@ pub const UUID = packed union { pub fn new(options: NewOptions) UUID { switch (options) { .v1 => |v| { - const time = v.time orelse v1v6Time.now(); - const clock_seq = v.clock_seq orelse std.crypto.random.int(u14); - const node = v.node orelse (std.crypto.random.int(u48) | 0x1000_0000_0000); - + const time = v.time; + const clock_seq = switch (v.clock_seq) { + .raw => |raw| raw, + .random => |rng| rng.int(u14), + }; + const node = switch (v.node) { + .raw => |raw| raw, + .random => |rng| rng.int(u48) | 0x1000_0000_0000, + }; return .{ .v1 = .{ .time_low = time.v1.low, @@ -325,8 +349,8 @@ pub const UUID = packed union { }; }, - .v4 => { - var id: UUID = .{ .id = std.crypto.random.int(u128) }; + .v4 => |v| { + var id: UUID = .{ .id = v.rng.int(u128) }; id.v4.version = .v4; id.v4.variant = 0b10; return id; @@ -344,11 +368,16 @@ pub const UUID = packed union { }, .v6 => |v| { - const time = v.time orelse v1v6Time.now(); - const clock_seq = v.clock_seq orelse std.crypto.random.int(u14); + const time = v.time; + const clock_seq = switch (v.clock_seq) { + .raw => |raw| raw, + .random => |rng| rng.int(u14), + }; // if we didn't get a node, create a random one and set the multicast bit - const node = v.node orelse (std.crypto.random.int(u48) | 0x100000000000); - + const node = switch (v.node) { + .raw => |raw| raw, + .random => |rng| rng.int(u48) | 0x100000000000, + }; return .{ .v6 = .{ .time_low = time.v6.low, @@ -361,8 +390,11 @@ pub const UUID = packed union { }, .v7 => |v| { - var id: UUID = .{ .id = std.crypto.random.int(u128) }; - id.v7.unix_ts_ms = v.unix_ts_ms orelse @intCast(std.time.milliTimestamp()); + var id: UUID = .{ .id = v.rng.int(u128) }; + id.v7.unix_ts_ms = switch (v.unix_ts_ms) { + .raw => |raw| raw, + .timestamp => |ts| @intCast(ts.toMilliseconds()), + }; id.v7.version = .v7; id.v7.variant = 0b10; return id; @@ -371,9 +403,9 @@ pub const UUID = packed union { .v8 => |v| { return .{ .v8 = .{ - .custom_a = v.part.a, - .custom_b = v.part.b, - .custom_c = v.part.c, + .custom_a = v.parts.a, + .custom_b = v.parts.b, + .custom_c = v.parts.c, }, }; }, @@ -396,8 +428,8 @@ pub const UUID = packed union { return buf; } - pub fn serializeZ(self: UUID) [36:0]u8 { - var buf: [36:0]u8 = undefined; + pub fn serializeSentinel(self: UUID, comptime sentinel: u8) [36:sentinel]u8 { + var buf: [36:sentinel]u8 = undefined; _ = std.fmt.bufPrint( &buf, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>4}-{x:0>12}", @@ -409,7 +441,7 @@ pub const UUID = packed union { self.serializable.e, }, ) catch unreachable; - buf[buf.len] = 0; + buf[buf.len] = sentinel; return buf; } @@ -444,8 +476,8 @@ pub const UUID = packed union { return buf; } - pub fn serializeUrnZ(self: UUID) [45:0]u8 { - var buf: [45:0]u8 = undefined; + pub fn serializeUrnSentinel(self: UUID, comptime sentinel: u8) [45:sentinel]u8 { + var buf: [45:sentinel]u8 = undefined; @memcpy(buf[0..9], "urn:uuid:"); _ = std.fmt.bufPrint( buf[9..], @@ -458,7 +490,7 @@ pub const UUID = packed union { self.serializable.e, }, ) catch unreachable; - buf[buf.len] = 0; + buf[buf.len] = sentinel; return buf; } @@ -470,14 +502,27 @@ pub const UUID = packed union { }; test "uuid test 1" { - const id1: UUID = .new(.v4); + const rng_impl: std.Random.IoSource = .{ .io = std.testing.io }; + const rng = rng_impl.interface(); + const id1: UUID = .new(.{ .v4 = .{ .rng = rng } }); const str = id1.serialize(); const id2 = try UUID.deserialize(&str); try std.testing.expectEqual(id2.id, id1.id); } test "uuid test 2" { - const id1: UUID = .new(.{ .v7 = .{} }); + const rng_impl: std.Random.IoSource = .{ .io = std.testing.io }; + const rng = rng_impl.interface(); + const id1: UUID = .new( + .{ + .v7 = .{ + .unix_ts_ms = .{ + .timestamp = .now(std.testing.io, .real), + }, + .rng = rng, + }, + }, + ); const str = id1.serialize(); const id2 = try UUID.deserialize(&str); try std.testing.expectEqual(id2.id, id1.id); @@ -488,11 +533,11 @@ test "uuid test 3" { // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv1-value const id: UUID = .{ .v1 = .{ - .time_low = 0xC232AB00, + .time_low = 0xc232ab00, .time_mid = 0x9414, - .time_high = 0x1EC, - .clock_seq = 0x33C8, - .node = 0x9F6BDECED846, + .time_high = 0x1ec, + .clock_seq = 0x33c8, + .node = 0x9f6bdeced846, }, }; @@ -501,7 +546,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("c232ab00-9414-11ec-b3c8-9f6bdeced846", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "c232ab00-9414-11ec-b3c8-9f6bdeced846", &str); } } @@ -512,10 +557,14 @@ test "uuid test 3" { .{ .v1 = .{ .time = .{ - .raw = 0x1EC9414C232AB00, + .raw = 0x1ec9414c232ab00, + }, + .clock_seq = .{ + .raw = 0x33C8, + }, + .node = .{ + .raw = 0x9f6bdeced846, }, - .clock_seq = 0x33C8, - .node = 0x9F6BDECED846, }, }, ); @@ -528,7 +577,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("c232ab00-9414-11ec-b3c8-9f6bdeced846", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "c232ab00-9414-11ec-b3c8-9f6bdeced846", &str); } } @@ -552,7 +601,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("5df41881-3aed-3515-88a7-2f4a814cf09e", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "5df41881-3aed-3515-88a7-2f4a814cf09e", &str); } } @@ -574,7 +623,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("5df41881-3aed-3515-88a7-2f4a814cf09e", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "5df41881-3aed-3515-88a7-2f4a814cf09e", &str); } } @@ -597,7 +646,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("919108f7-52d1-4320-9bac-f847db4148a8", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "919108f7-52d1-4320-9bac-f847db4148a8", &str); } } @@ -620,7 +669,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("2ed6657d-e927-568b-95e1-2665a8aea6a2", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "2ed6657d-e927-568b-95e1-2665a8aea6a2", &str); } } @@ -643,7 +692,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("2ed6657d-e927-568b-95e1-2665a8aea6a2", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "2ed6657d-e927-568b-95e1-2665a8aea6a2", &str); } } @@ -652,11 +701,11 @@ test "uuid test 3" { // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv6-value const id: UUID = .{ .v6 = .{ - .time_high = 0x1EC9414C, - .time_mid = 0x232A, - .time_low = 0xB00, - .clock_seq = 0x33C8, - .node = 0x9F6BDECED846, + .time_high = 0x1ec9414c, + .time_mid = 0x232a, + .time_low = 0xb00, + .clock_seq = 0x33c8, + .node = 0x9f6bdeced846, }, }; @@ -668,7 +717,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("1ec9414c-232a-6b00-b3c8-9f6bdeced846", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "1ec9414c-232a-6b00-b3c8-9f6bdeced846", &str); } } @@ -677,9 +726,9 @@ test "uuid test 3" { // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value const id: UUID = .{ .v7 = .{ - .unix_ts_ms = 0x017F22E279B0, - .rand_a = 0xCC3, - .rand_b = 0x18C4DC0C0C07398F, + .unix_ts_ms = 0x017f22e279b0, + .rand_a = 0xcc3, + .rand_b = 0x18c4dc0c0c07398f, }, }; @@ -691,7 +740,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("017f22e2-79b0-7cc3-98c4-dc0c0c07398f", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "017f22e2-79b0-7cc3-98c4-dc0c0c07398f", &str); } } @@ -700,9 +749,9 @@ test "uuid test 3" { // https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv8-value-t const id: UUID = .{ .v8 = .{ - .custom_a = 0x2489E9AD2EE2, - .custom_b = 0xE00, - .custom_c = 0x0EC932D5F69181C0, + .custom_a = 0x2489e9ad2ee2, + .custom_b = 0xe00, + .custom_c = 0x0ec932d5f69181c0, }, }; @@ -714,7 +763,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("2489e9ad-2ee2-8e00-8ec9-32d5f69181c0", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "2489e9ad-2ee2-8e00-8ec9-32d5f69181c0", &str); } } @@ -738,7 +787,7 @@ test "uuid test 3" { try std.testing.expectEqualStrings("5c146b14-3c52-8afd-938a-375d0df1fbf6", &str); } { - const str = id.serializeZ(); + const str = id.serializeSentinel(0); try std.testing.expectEqualSentinel(u8, 0, "5c146b14-3c52-8afd-938a-375d0df1fbf6", &str); } }