Repository: oxidecomputer/typify (rust)

Issue:
Adding a `"maximum"` limit to an integer changes the inner Rust type
In the following JSON schema, all three `ByteCountN` variants have the same `type` and `format`, and only differ by `maximum`:

* `ByteCount1` omits `maximum`
* `ByteCount2` sets its `maximum` to `i64::MAX`
* `ByteCount3` sets its `maximum` to `i64::MAX + 1`

```json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "definitions": {
        "ByteCount1": {
            "description": "Byte count to express memory or storage capacity.",
            "type": "integer",
            "format": "uint64",
            "minimum": 0
        },
        "ByteCount2": {
            "description": "Byte count to express memory or storage capacity.",
            "type": "integer",
            "format": "uint64",
            "minimum": 0,
            "maximum": 9223372036854775807
        },
        "ByteCount3": {
            "description": "Byte count to express memory or storage capacity.",
            "type": "integer",
            "format": "uint64",
            "minimum": 0,
            "maximum": 9223372036854775808
        }
    }
}
```

I expected all of these to produce a struct wrapping a `u64`, but `ByteCount{2,3}` use `i64` instead:

```
% cargo typify bytecount.json && rg 'struct ByteCount' bytecount.rs
48:pub struct ByteCount1(pub u64);
114:pub struct ByteCount2(pub i64);
180:pub struct ByteCount3(pub i64);
```

Pull request:
fixes #589 allow min and max to be within range
fixes #589 

Allow min and max to be within range of the specified format

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 31b9de5..337266f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -120,9 +120,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "backtrace"
-version = "0.3.69"
+version = "0.3.71"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
 dependencies = [
  "addr2line",
  "cc",
@@ -182,11 +182,11 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.83"
+version = "1.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
 dependencies = [
- "libc",
+ "shlex",
 ]
 
 [[package]]
@@ -197,9 +197,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "chrono"
-version = "0.4.39"
+version = "0.4.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
 dependencies = [
  "android-tzdata",
  "iana-time-zone",
@@ -207,14 +207,14 @@ dependencies = [
  "num-traits",
  "serde",
  "wasm-bindgen",
- "windows-targets 0.52.0",
+ "windows-link",
 ]
 
 [[package]]
 name = "clap"
-version = "4.5.32"
+version = "4.5.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
+checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -222,9 +222,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.32"
+version = "4.5.36"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
+checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
 dependencies = [
  "anstream",
  "anstyle",
@@ -320,12 +320,6 @@ version = "1.0.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
 
-[[package]]
-name = "either"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
-
 [[package]]
 name = "encode_unicode"
 version = "0.3.6"
@@ -344,9 +338,9 @@ dependencies = [
 
 [[package]]
 name = "env_logger"
-version = "0.11.7"
+version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
+checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
 dependencies = [
  "anstream",
  "anstyle",
@@ -529,15 +523,6 @@ version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
 
-[[package]]
-name = "itertools"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.10"
@@ -597,9 +582,9 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
 
 [[package]]
 name = "log"
-version = "0.4.26"
+version = "0.4.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 
 [[package]]
 name = "memchr"
@@ -609,9 +594,9 @@ checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.7.1"
+version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
 dependencies = [
  "adler",
 ]
@@ -636,9 +621,9 @@ dependencies = [
 
 [[package]]
 name = "object"
-version = "0.32.1"
+version = "0.32.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
 dependencies = [
  "memchr",
 ]
@@ -663,9 +648,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
 [[package]]
 name = "pin-project-lite"
-version = "0.2.13"
+version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 
 [[package]]
 name = "portable-atomic"
@@ -684,27 +669,26 @@ dependencies = [
 
 [[package]]
 name = "predicates"
-version = "3.0.4"
+version = "3.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0"
+checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
 dependencies = [
  "anstyle",
  "difflib",
- "itertools",
  "predicates-core",
 ]
 
 [[package]]
 name = "predicates-core"
-version = "1.0.6"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
+checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
 
 [[package]]
 name = "predicates-tree"
-version = "1.0.9"
+version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
+checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
 dependencies = [
  "predicates-core",
  "termtree",
@@ -712,9 +696,9 @@ dependencies = [
 
 [[package]]
 name = "prettyplease"
-version = "0.2.29"
+version = "0.2.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
+checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
 dependencies = [
  "proc-macro2",
  "syn",
@@ -834,9 +818,9 @@ dependencies = [
 
 [[package]]
 name = "rustc-demangle"
-version = "0.1.23"
+version = "0.1.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
 [[package]]
 name = "rustfmt-wrapper"
@@ -1010,6 +994,12 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
 [[package]]
 name = "similar"
 version = "2.3.0"
@@ -1018,9 +1008,9 @@ checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597"
 
 [[package]]
 name = "strsim"
-version = "0.11.0"
+version = "0.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
 
 [[package]]
 name = "syn"
@@ -1073,9 +1063,9 @@ dependencies = [
 
 [[package]]
 name = "termtree"
-version = "0.4.1"
+version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
+checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
 
 [[package]]
 name = "thiserror"
@@ -1119,9 +1109,9 @@ dependencies = [
 
 [[package]]
 name = "thread_local"
-version = "1.1.7"
+version = "1.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -1176,9 +1166,9 @@ dependencies = [
 
 [[package]]
 name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "pin-project-lite",
  "tracing-core",
@@ -1186,9 +1176,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
 dependencies = [
  "once_cell",
  "valuable",
@@ -1196,9 +1186,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-error"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
+checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db"
 dependencies = [
  "tracing",
  "tracing-subscriber",
@@ -1206,9 +1196,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.3.18"
+version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
 dependencies = [
  "sharded-slab",
  "thread_local",
@@ -1305,9 +1295,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.17"
+version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
 name = "unicode-segmentation"
@@ -1338,15 +1328,15 @@ dependencies = [
 
 [[package]]
 name = "valuable"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
 
 [[package]]
 name = "wait-timeout"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
 dependencies = [
  "libc",
 ]
@@ -1455,6 +1445,12 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
+[[package]]
+name = "windows-link"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
+
 [[package]]
 name = "windows-sys"
 version = "0.45.0"
diff --git a/Cargo.toml b/Cargo.toml
index 09f75c1..1a24607 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,36 @@ members = [
 
 resolver = "2"
 
-#[patch.crates-io]
-#serde_tokenstream = { path = "../serde_tokenstream" }
+[workspace.dependencies]
+typify = { version = "0.3.0", path = "typify" }
+typify-impl = { version = "0.3.0", path = "typify-impl" }
+typify-macro = { version = "0.3.0", path = "typify-macro" }
 
+assert_cmd = "2.0.16"
+chrono = { version = "0.4.39", features = ["serde"] }
+clap = { version = "4.5.36", features = ["derive"] }
+color-eyre = "0.6"
+env_logger = "0.11"
+expectorate = "1.1.0"
+glob = "0.3.2"
+heck = "0.5.0"
+ipnetwork = { version = "0.21.1", features = ["schemars"] }
+log = "0.4.27"
+newline-converter = "0.3.0"
+paste = "1.0.15"
+prettyplease = "0.2.32"
+proc-macro2 = "1.0.94"
+quote = "1.0.40"
+regress = "0.10.3"
+rustfmt-wrapper = "0.2.1"
+schema = "0.1.0"
+schemars = "0.8.22"
+semver = "1.0.26"
+serde = "1.0.219"
+serde_json = "1.0.140"
+syn = { version = "2.0.100", features = ["full"] }
+tempdir = "0.3.7"
+thiserror = "2.0.12"
+trybuild = "1.0.104"
+unicode-ident = "1.0.18"
+uuid = "1.16.0"
diff --git a/cargo-typify/Cargo.toml b/cargo-typify/Cargo.toml
index 34733e6..0bf61b8 100644
--- a/cargo-typify/Cargo.toml
+++ b/cargo-typify/Cargo.toml
@@ -12,18 +12,18 @@ categories = ["api-bindings", "compilers"]
 default-run = "cargo-typify"
 
 [dependencies]
-typify = { version = "0.3.0", path = "../typify" }
+typify = { workspace = true }
 
-clap = { version = "4.5.32", features = ["derive"] }
-color-eyre = "0.6"
-env_logger = "0.11"
-rustfmt-wrapper = "0.2.1"
-semver = "1.0.26"
-serde_json = "1.0.140"
-schemars = "0.8.22"
+clap = { workspace = true }
+color-eyre = { workspace = true }
+env_logger = { workspace = true }
+rustfmt-wrapper = { workspace = true }
+semver = { workspace = true }
+serde_json = { workspace = true }
+schemars = { workspace = true }
 
 [dev-dependencies]
-assert_cmd = "2.0.16"
-expectorate = "1.1.0"
-newline-converter = "0.3.0"
-tempdir = "0.3.7"
+assert_cmd = { workspace = true }
+expectorate = { workspace = true }
+newline-converter = { workspace = true }
+tempdir = { workspace = true }
diff --git a/typify-impl/Cargo.toml b/typify-impl/Cargo.toml
index 26c6d67..a07be5e 100644
--- a/typify-impl/Cargo.toml
+++ b/typify-impl/Cargo.toml
@@ -8,26 +8,25 @@ repository = "https://github.com/oxidecomputer/typify"
 readme = "../README.md"
 
 [dependencies]
-heck = "0.5.0"
-log = "0.4.26"
-proc-macro2 = "1.0.94"
-quote = "1.0.40"
-regress = "0.10.3"
-schemars = "0.8.22"
-semver = "1.0.26"
-serde = "1.0.219"
-serde_json = "1.0.140"
-syn = { version = "2.0.100", features = ["full"] }
-thiserror = "2.0.12"
-unicode-ident = "1.0.17"
-
+heck = { workspace = true }
+log = { workspace = true }
+proc-macro2 = { workspace = true }
+quote = { workspace = true }
+regress = { workspace = true }
+schemars = { workspace = true }
+semver = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+syn = { workspace = true }
+thiserror = { workspace = true }
+unicode-ident = { workspace = true }
 
 [dev-dependencies]
-env_logger = "0.11.7"
-expectorate = "1.1.0"
-paste = "1.0.15"
-rustfmt-wrapper = "0.2.1"
-schema = "0.1.0"
-schemars = { version = "0.8.22", features = ["uuid1", "impl_json_schema"] }
-syn = { version = "2.0.100", features = ["full", "extra-traits", "visit-mut"] }
-uuid = "1.16.0"
+env_logger = { workspace = true }
+expectorate = { workspace = true }
+paste = { workspace = true }
+rustfmt-wrapper = { workspace = true }
+schema = { workspace = true }
+schemars = { workspace = true, features = ["uuid1", "impl_json_schema"] }
+syn = { workspace = true, features = ["full", "extra-traits", "visit-mut"] }
+uuid = { workspace = true }
diff --git a/typify-impl/src/convert.rs b/typify-impl/src/convert.rs
index ff8bd47..6e877d7 100644
--- a/typify-impl/src/convert.rs
+++ b/typify-impl/src/convert.rs
@@ -991,35 +991,90 @@ impl TypeSpace {
         };
 
         // Ordered from most- to least-restrictive.
-        let formats: &[(&str, &str, f64, f64)] = &[
-            ("int8", "i8", i8::MIN as f64, i8::MAX as f64),
-            ("", "::std::num::NonZeroU8", 1.0, u8::MAX as f64),
-            ("uint8", "u8", u8::MIN as f64, u8::MAX as f64),
-            ("int16", "i16", i16::MIN as f64, i16::MAX as f64),
-            ("", "::std::num::NonZeroU16", 1.0, u16::MAX as f64),
-            ("uint16", "u16", u16::MIN as f64, u16::MAX as f64),
-            ("int", "i32", i32::MIN as f64, i32::MAX as f64),
-            ("int32", "i32", i32::MIN as f64, i32::MAX as f64),
-            ("", "::std::num::NonZeroU32", 1.0, u32::MAX as f64),
-            ("uint", "u32", u32::MIN as f64, u32::MAX as f64),
-            ("uint32", "u32", u32::MIN as f64, u32::MAX as f64),
+        // JSONSchema format, Rust Type, Rust NonZero Type, Rust type min, Rust type max
+        let formats: &[(&str, &str, &str, f64, f64)] = &[
+            (
+                "int8",
+                "i8",
+                "::std::num::NonZeroU8",
+                i8::MIN as f64,
+                i8::MAX as f64,
+            ),
+            (
+                "uint8",
+                "u8",
+                "::std::num::NonZeroU8",
+                u8::MIN as f64,
+                u8::MAX as f64,
+            ),
+            (
+                "int16",
+                "i16",
+                "::std::num::NonZeroU16",
+                i16::MIN as f64,
+                i16::MAX as f64,
+            ),
+            (
+                "uint16",
+                "u16",
+                "::std::num::NonZeroU16",
+                u16::MIN as f64,
+                u16::MAX as f64,
+            ),
+            (
+                "int",
+                "i32",
+                "::std::num::NonZeroU32",
+                i32::MIN as f64,
+                i32::MAX as f64,
+            ),
+            (
+                "int32",
+                "i32",
+                "::std::num::NonZeroU32",
+                i32::MIN as f64,
+                i32::MAX as f64,
+            ),
+            (
+                "uint",
+                "u32",
+                "::std::num::NonZeroU32",
+                u32::MIN as f64,
+                u32::MAX as f64,
+            ),
+            (
+                "uint32",
+                "u32",
+                "::std::num::NonZeroU32",
+                u32::MIN as f64,
+                u32::MAX as f64,
+            ),
             // TODO all these are wrong as casting to an f64 loses precision.
             // However, schemars stores everything as an f64 so... meh for now.
-            ("int64", "i64", i64::MIN as f64, i64::MAX as f64),
-            ("", "::std::num::NonZeroU64", 1.0, u64::MAX as f64),
-            ("uint64", "u64", u64::MIN as f64, u64::MAX as f64),
+            (
+                "int64",
+                "i64",
+                "::std::num::NonZeroU64",
+                i64::MIN as f64,
+                i64::MAX as f64,
+            ),
+            (
+                "uint64",
+                "u64",
+                "::std::num::NonZeroU64",
+                u64::MIN as f64,
+                u64::MAX as f64,
+            ),
         ];
 
         if let Some(format) = format {
-            if let Some((_, ty, imin, imax)) = formats
+            if let Some((_fmt, ty, nz_ty, imin, imax)) = formats
                 .iter()
-                .find(|(int_format, _, _, _)| int_format == format)
+                .find(|(int_format, _, _, _, _)| int_format == format)
             {
-                // If the type matches with other constraints, we're done.
-                if multiple.is_none()
-                    && (min.is_none() || min == Some(*imin))
-                    && (max.is_none() || max == Some(*imax))
-                {
+                let valid_min = min.is_none() || min.map(|fmin| fmin.ge(imin)).unwrap_or(false);
+                let valid_max = max.is_none() || max.map(|fmax| fmax.le(imax)).unwrap_or(false);
+                if multiple.is_none() && valid_min && valid_max {
                     // If there's a default value and it's either not a number
                     // or outside of the range for this format, return an
                     // error.
@@ -1032,7 +1087,13 @@ impl TypeSpace {
                             return Err(Error::InvalidValue);
                         }
                     }
-                    return Ok((TypeEntry::new_integer(ty), metadata));
+
+                    // Use NonZero types for minimum 1
+                    if min == Some(1.) {
+                        return Ok((TypeEntry::new_integer(nz_ty), metadata));
+                    } else {
+                        return Ok((TypeEntry::new_integer(ty), metadata));
+                    }
                 }
 
                 if min.is_none() {
@@ -1063,27 +1124,35 @@ impl TypeSpace {
 
         // See if the value bounds fit within a known type.
         let maybe_type = match (min, max) {
-            (None, Some(max)) => formats.iter().rev().find_map(|(_, ty, _, imax)| {
+            (None, Some(max)) => formats.iter().rev().find_map(|(_, ty, _nz_ty, _, imax)| {
                 if (imax - max).abs() <= f64::EPSILON {
                     Some(ty.to_string())
                 } else {
                     None
                 }
             }),
-            (Some(min), None) => formats.iter().rev().find_map(|(_, ty, imin, _)| {
-                if (imin - min).abs() <= f64::EPSILON {
-                    Some(ty.to_string())
-                } else {
-                    None
-                }
-            }),
-            (Some(min), Some(max)) => formats.iter().rev().find_map(|(_, ty, imin, imax)| {
-                if (imax - max).abs() <= f64::EPSILON && (imin - min).abs() <= f64::EPSILON {
+            (Some(min), None) => formats.iter().rev().find_map(|(_, ty, nz_ty, imin, _)| {
+                if min == 1. {
+                    Some(nz_ty.to_string())
+                } else if (imin - min).abs() <= f64::EPSILON {
                     Some(ty.to_string())
                 } else {
                     None
                 }
             }),
+            (Some(min), Some(max)) => {
+                formats.iter().rev().find_map(|(_, ty, nz_ty, imin, imax)| {
+                    if min == 1. {
+                        Some(nz_ty.to_string())
+                    } else if (imax - max).abs() <= f64::EPSILON
+                        && (imin - min).abs() <= f64::EPSILON
+                    {
+                        Some(ty.to_string())
+                    } else {
+                        None
+                    }
+                })
+            }
             (None, None) => None,
         };
 
diff --git a/typify/Cargo.toml b/typify/Cargo.toml
index 29027ec..5f18266 100644
--- a/typify/Cargo.toml
+++ b/typify/Cargo.toml
@@ -14,19 +14,19 @@ default = ["macro"]
 macro = ["typify-macro"]
 
 [dependencies]
-typify-macro = { version = "0.3.0", path = "../typify-macro", optional = true }
-typify-impl = { version = "0.3.0", path = "../typify-impl" }
+typify-macro = { workspace = true, optional = true }
+typify-impl = { workspace = true }
 
 [dev-dependencies]
-chrono = { version = "0.4.39", features = ["serde"] }
-env_logger = "0.11.7"
-expectorate = "1.1.0"
-glob = "0.3.2"
-quote = "1.0.40"
-regress = "0.10.3"
-rustfmt-wrapper = "0.2.1"
-schemars = "0.8.22"
-serde = "1.0.219"
-serde_json = "1.0.140"
-trybuild = "1.0.104"
-uuid = { version = "1.16.0", features = ["serde"] }
+chrono = { workspace = true }
+env_logger = { workspace = true }
+expectorate = { workspace = true }
+glob = { workspace = true }
+quote = { workspace = true }
+regress = { workspace = true }
+rustfmt-wrapper = { workspace = true }
+schemars = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
+trybuild = { workspace = true }
+uuid = { workspace = true, features = ["serde"] }


Test patch:
diff --git a/typify-test/Cargo.toml b/typify-test/Cargo.toml
index a3c6b45..8e13428 100644
--- a/typify-test/Cargo.toml
+++ b/typify-test/Cargo.toml
@@ -4,14 +4,15 @@ version = "0.0.0"
 edition = "2021"
 
 [dependencies]
-regress = "0.10.3"
-serde = "1.0.219"
-serde_json = "1.0.140"
+regress = { workspace = true }
+serde = { workspace = true }
+serde_json = { workspace = true }
 
 [build-dependencies]
-ipnetwork = { version = "0.21.1", features = ["schemars"] }
-prettyplease = "0.2.29"
-schemars = "0.8.22"
-serde = "1.0.219"
-syn = "2.0.100"
 typify = { path = "../typify" }
+
+ipnetwork = { workspace = true }
+prettyplease = { workspace = true }
+schemars = { workspace = true }
+serde = { workspace = true }
+syn = { workspace = true }
diff --git a/typify/tests/schemas/simple-types.json b/typify/tests/schemas/simple-types.json
index 380e8e2..e12cadd 100644
--- a/typify/tests/schemas/simple-types.json
+++ b/typify/tests/schemas/simple-types.json
@@ -20,6 +20,48 @@
       "required": [
         "value"
       ]
+    },
+    "uint-minimum-and-maximum": {
+      "type": "object",
+      "required": [
+        "max",
+        "min",
+        "min_and_max",
+        "min_non_zero",
+        "min_uint_non_zero",
+        "no_bounds"
+      ],
+      "properties": {
+        "no_bounds": {
+          "type": "integer",
+          "format": "uint64"
+        },
+        "min": {
+          "type": "integer",
+          "format": "uint64",
+          "minimum": 0
+        },
+        "min_non_zero": {
+          "type": "integer",
+          "minimum": 1
+        },
+        "min_uint_non_zero": {
+          "type": "integer",
+          "format": "uint64",
+          "minimum": 1
+        },
+        "max": {
+          "type": "integer",
+          "format": "uint64",
+          "maximum": 256
+        },
+        "min_and_max": {
+          "type": "integer",
+          "format": "uint64",
+          "minimum": 1,
+          "maximum": 256
+        }
+      }
     }
   }
-}
\ No newline at end of file
+}
diff --git a/typify/tests/schemas/simple-types.rs b/typify/tests/schemas/simple-types.rs
index 56a27f7..5375e1e 100644
--- a/typify/tests/schemas/simple-types.rs
+++ b/typify/tests/schemas/simple-types.rs
@@ -147,6 +147,74 @@ impl ::std::fmt::Display for JustOne {
         self.0.fmt(f)
     }
 }
+#[doc = "`UintMinimumAndMaximum`"]
+#[doc = r""]
+#[doc = r" <details><summary>JSON schema</summary>"]
+#[doc = r""]
+#[doc = r" ```json"]
+#[doc = "{"]
+#[doc = "  \"type\": \"object\","]
+#[doc = "  \"required\": ["]
+#[doc = "    \"max\","]
+#[doc = "    \"min\","]
+#[doc = "    \"min_and_max\","]
+#[doc = "    \"min_non_zero\","]
+#[doc = "    \"min_uint_non_zero\","]
+#[doc = "    \"no_bounds\""]
+#[doc = "  ],"]
+#[doc = "  \"properties\": {"]
+#[doc = "    \"max\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"format\": \"uint64\","]
+#[doc = "      \"maximum\": 256.0"]
+#[doc = "    },"]
+#[doc = "    \"min\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"format\": \"uint64\","]
+#[doc = "      \"minimum\": 0.0"]
+#[doc = "    },"]
+#[doc = "    \"min_and_max\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"format\": \"uint64\","]
+#[doc = "      \"maximum\": 256.0,"]
+#[doc = "      \"minimum\": 1.0"]
+#[doc = "    },"]
+#[doc = "    \"min_non_zero\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"minimum\": 1.0"]
+#[doc = "    },"]
+#[doc = "    \"min_uint_non_zero\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"format\": \"uint64\","]
+#[doc = "      \"minimum\": 1.0"]
+#[doc = "    },"]
+#[doc = "    \"no_bounds\": {"]
+#[doc = "      \"type\": \"integer\","]
+#[doc = "      \"format\": \"uint64\""]
+#[doc = "    }"]
+#[doc = "  }"]
+#[doc = "}"]
+#[doc = r" ```"]
+#[doc = r" </details>"]
+#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
+pub struct UintMinimumAndMaximum {
+    pub max: u64,
+    pub min: u64,
+    pub min_and_max: ::std::num::NonZeroU64,
+    pub min_non_zero: ::std::num::NonZeroU64,
+    pub min_uint_non_zero: ::std::num::NonZeroU64,
+    pub no_bounds: u64,
+}
+impl ::std::convert::From<&UintMinimumAndMaximum> for UintMinimumAndMaximum {
+    fn from(value: &UintMinimumAndMaximum) -> Self {
+        value.clone()
+    }
+}
+impl UintMinimumAndMaximum {
+    pub fn builder() -> builder::UintMinimumAndMaximum {
+        Default::default()
+    }
+}
 #[doc = r" Types for composing complex structures."]
 pub mod builder {
     #[derive(Clone, Debug)]
@@ -229,5 +297,118 @@ pub mod builder {
             }
         }
     }
+    #[derive(Clone, Debug)]
+    pub struct UintMinimumAndMaximum {
+        max: ::std::result::Result<u64, ::std::string::String>,
+        min: ::std::result::Result<u64, ::std::string::String>,
+        min_and_max: ::std::result::Result<::std::num::NonZeroU64, ::std::string::String>,
+        min_non_zero: ::std::result::Result<::std::num::NonZeroU64, ::std::string::String>,
+        min_uint_non_zero: ::std::result::Result<::std::num::NonZeroU64, ::std::string::String>,
+        no_bounds: ::std::result::Result<u64, ::std::string::String>,
+    }
+    impl ::std::default::Default for UintMinimumAndMaximum {
+        fn default() -> Self {
+            Self {
+                max: Err("no value supplied for max".to_string()),
+                min: Err("no value supplied for min".to_string()),
+                min_and_max: Err("no value supplied for min_and_max".to_string()),
+                min_non_zero: Err("no value supplied for min_non_zero".to_string()),
+                min_uint_non_zero: Err("no value supplied for min_uint_non_zero".to_string()),
+                no_bounds: Err("no value supplied for no_bounds".to_string()),
+            }
+        }
+    }
+    impl UintMinimumAndMaximum {
+        pub fn max<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<u64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.max = value
+                .try_into()
+                .map_err(|e| format!("error converting supplied value for max: {}", e));
+            self
+        }
+        pub fn min<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<u64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.min = value
+                .try_into()
+                .map_err(|e| format!("error converting supplied value for min: {}", e));
+            self
+        }
+        pub fn min_and_max<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<::std::num::NonZeroU64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.min_and_max = value
+                .try_into()
+                .map_err(|e| format!("error converting supplied value for min_and_max: {}", e));
+            self
+        }
+        pub fn min_non_zero<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<::std::num::NonZeroU64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.min_non_zero = value
+                .try_into()
+                .map_err(|e| format!("error converting supplied value for min_non_zero: {}", e));
+            self
+        }
+        pub fn min_uint_non_zero<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<::std::num::NonZeroU64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.min_uint_non_zero = value.try_into().map_err(|e| {
+                format!(
+                    "error converting supplied value for min_uint_non_zero: {}",
+                    e
+                )
+            });
+            self
+        }
+        pub fn no_bounds<T>(mut self, value: T) -> Self
+        where
+            T: ::std::convert::TryInto<u64>,
+            T::Error: ::std::fmt::Display,
+        {
+            self.no_bounds = value
+                .try_into()
+                .map_err(|e| format!("error converting supplied value for no_bounds: {}", e));
+            self
+        }
+    }
+    impl ::std::convert::TryFrom<UintMinimumAndMaximum> for super::UintMinimumAndMaximum {
+        type Error = super::error::ConversionError;
+        fn try_from(
+            value: UintMinimumAndMaximum,
+        ) -> ::std::result::Result<Self, super::error::ConversionError> {
+            Ok(Self {
+                max: value.max?,
+                min: value.min?,
+                min_and_max: value.min_and_max?,
+                min_non_zero: value.min_non_zero?,
+                min_uint_non_zero: value.min_uint_non_zero?,
+                no_bounds: value.no_bounds?,
+            })
+        }
+    }
+    impl ::std::convert::From<super::UintMinimumAndMaximum> for UintMinimumAndMaximum {
+        fn from(value: super::UintMinimumAndMaximum) -> Self {
+            Self {
+                max: Ok(value.max),
+                min: Ok(value.min),
+                min_and_max: Ok(value.min_and_max),
+                min_non_zero: Ok(value.min_non_zero),
+                min_uint_non_zero: Ok(value.min_uint_non_zero),
+                no_bounds: Ok(value.no_bounds),
+            }
+        }
+    }
 }
 fn main() {}