diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d99981..33e5057 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,11 +103,20 @@ jobs: uses: actions-rs/toolchain@v1 with: toolchain: stable + target: thumbv7m-none-eabi # Needed for no_std_example - name: Run examples run: | - for example in $(ls ./examples/); do - cargo run --bin $example + set -euxo pipefail + ROOT_DIR=$(pwd) + for EXAMPLE in `ls examples`; do + cd $ROOT_DIR/examples/$EXAMPLE; + if [[ "$EXAMPLE" == "no_std_example" ]] + then + cargo build + else + cargo run + fi done typos: diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6338f..6fa73d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### v0.4.1 - xxxx-xx-xx +* Support `no_std` ( the dependency needs to be declared as `nutype = { default-features = false }` ) * Support integration with [`arbitrary`](https://crates.io/crates/arbitrary) crate (see `arbitrary` feature). * Support `Arbitrary` for integer types * Support `Arbitrary` for float types diff --git a/Cargo.toml b/Cargo.toml index e56d3d7..35cab2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,16 @@ members = [ "nutype_macros", "test_suite", "dummy", - "examples/*", + + # All examples except "no_std_example" are tested in the test suite + "examples/any_arbitrary", + "examples/float_arbitrary", + "examples/float_sortable", + "examples/integer_arbitrary", + "examples/integer_bounded", + "examples/new_unchecked_example", + # "examples/no_std_example", + "examples/serde_complex", + "examples/string_bounded_len", + "examples/string_regex_email", ] diff --git a/Justfile b/Justfile index 98e17cf..be61fb6 100644 --- a/Justfile +++ b/Justfile @@ -27,7 +27,18 @@ clippy: cargo clippy -- -D warnings examples: - for example in `ls examples`; do cargo run --bin $example; done + #!/usr/bin/env bash + set -euxo pipefail + ROOT_DIR=$(pwd) + for EXAMPLE in `ls examples`; do + cd $ROOT_DIR/examples/$EXAMPLE; + if [[ "$EXAMPLE" == "no_std_example" ]] + then + cargo build + else + cargo run + fi + done typos: which typos >/dev/null || cargo install typos-cli diff --git a/README.md b/README.md index b4a4182..c373ad1 100644 --- a/README.md +++ b/README.md @@ -354,10 +354,12 @@ assert_eq!(name.into_inner(), " boo "); ## Feature flags -* `serde` - integrations with [`serde`](https://crates.io/crates/serde) crate. Allows to derive `Serialize` and `Deserialize` traits. +* `arbitrary` - enables derive of [`arbitrary::Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/trait.Arbitrary.html). +* `new_unchecked` - enables generation of unsafe `::new_unchecked()` function. * `regex` - allows to use `regex = ` validation on string-based types. Note: your crate also has to explicitly have `regex` and `lazy_static` within dependencies. +* `serde` - integrations with [`serde`](https://crates.io/crates/serde) crate. Allows to derive `Serialize` and `Deserialize` traits. * `schemars08` - allows to derive [`JsonSchema`](https://docs.rs/schemars/0.8.12/schemars/trait.JsonSchema.html) trait of [schemars](https://crates.io/crates/schemars) crate. Note that at the moment validation rules are not respected. -* `new_unchecked` - enables generation of unsafe `::new_unchecked()` function. +* `std` - enabled by default. Use `default-features = false` to disable. ## When nutype is a good fit for you? diff --git a/examples/no_std_example/.cargo/config b/examples/no_std_example/.cargo/config new file mode 100644 index 0000000..b800c6d --- /dev/null +++ b/examples/no_std_example/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "thumbv7m-none-eabi" diff --git a/examples/no_std_example/Cargo.lock b/examples/no_std_example/Cargo.lock new file mode 100644 index 0000000..1709dc6 --- /dev/null +++ b/examples/no_std_example/Cargo.lock @@ -0,0 +1,112 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "kinded" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4bdbb2f423660b19f0e9f7115182214732d8dd5f840cd0a3aee3e22562f34c" +dependencies = [ + "kinded_macros", +] + +[[package]] +name = "kinded_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13b4ddc5dcb32f45dac3d6f606da2a52fdb9964a18427e63cd5ef6c0d13288d" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "no_std_example" +version = "0.1.0" +dependencies = [ + "nutype", +] + +[[package]] +name = "nutype" +version = "0.4.0" +dependencies = [ + "nutype_macros", +] + +[[package]] +name = "nutype_macros" +version = "0.4.0" +dependencies = [ + "cfg-if", + "kinded", + "proc-macro2", + "quote", + "syn", + "urlencoding", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" diff --git a/examples/no_std_example/Cargo.toml b/examples/no_std_example/Cargo.toml new file mode 100644 index 0000000..57f9951 --- /dev/null +++ b/examples/no_std_example/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "no_std_example" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nutype = { path = "../../nutype", default-features = false } + +# Exclude this package from the common workspace, since it's no_std. +[workspace] diff --git a/examples/no_std_example/src/main.rs b/examples/no_std_example/src/main.rs new file mode 100644 index 0000000..187a54b --- /dev/null +++ b/examples/no_std_example/src/main.rs @@ -0,0 +1,74 @@ +// This example exists to ensure that code generated by nutype macro +// can compile in no_std environment. +#![no_main] +#![no_std] + +use core::panic::PanicInfo; +use nutype::nutype; + +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + loop {} +} + +// Integer +#[nutype( + validate(greater_or_equal = 1, less_or_equal = 6), + sanitize(with = |x| x), + derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + FromStr, + AsRef, + Deref, + TryFrom, + Into, + Hash, + Borrow, + Display, + Default, + ), + default = 4 +)] +struct GermanTaxClass(i64); + +// Float +#[nutype( + validate(greater_or_equal = 0.0, less_or_equal = 1024.0, finite), + sanitize(with = |x| x), + derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + FromStr, + AsRef, + Deref, + TryFrom, + Into, + Borrow, + Display, + Default, + ), + default = 0.0 +)] +struct Width(f64); + +// NOTE: strings are not working yet with no_std + +// Any other type +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Point { + x: i32, + y: i32, +} +#[nutype(derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, AsRef, Into, From, Deref, Borrow, Hash +))] +pub struct Location(Point); diff --git a/nutype/Cargo.toml b/nutype/Cargo.toml index a65ed26..7a6bb73 100644 --- a/nutype/Cargo.toml +++ b/nutype/Cargo.toml @@ -18,6 +18,9 @@ categories = ["data-structures", "rust-patterns"] nutype_macros = { version = "0.4.0", path = "../nutype_macros" } [features] +default = ["std"] + +std = ["nutype_macros/std"] serde = ["nutype_macros/serde"] regex = ["nutype_macros/regex"] schemars08 = ["nutype_macros/schemars08"] diff --git a/nutype/src/lib.rs b/nutype/src/lib.rs index 1759315..f8f7782 100644 --- a/nutype/src/lib.rs +++ b/nutype/src/lib.rs @@ -383,10 +383,12 @@ //! //! ## Feature flags //! -//! * `serde` - integrations with [`serde`](https://crates.io/crates/serde) crate. Allows to derive `Serialize` and `Deserialize` traits. +//! * `arbitrary` - enables derive of [`arbitrary::Arbitrary`](https://docs.rs/arbitrary/latest/arbitrary/trait.Arbitrary.html). //! * `new_unchecked` - enables generation of unsafe `::new_unchecked()` function. -//! * `schemars08` - allows to derive [`JsonSchema`](https://docs.rs/schemars/0.8.12/schemars/trait.JsonSchema.html) trait of [schemars](https://crates.io/crates/schemars) crate. Note that at the moment validation rules are not respected. //! * `regex` - allows to use `regex = ` validation on string-based types. Note: your crate also has to explicitly have `regex` and `lazy_static` within dependencies. +//! * `serde` - integrations with [`serde`](https://crates.io/crates/serde) crate. Allows to derive `Serialize` and `Deserialize` traits. +//! * `schemars08` - allows to derive [`JsonSchema`](https://docs.rs/schemars/0.8.12/schemars/trait.JsonSchema.html) trait of [schemars](https://crates.io/crates/schemars) crate. Note that at the moment validation rules are not respected. +//! * `std` - enabled by default. Use `default-features = false` to disable. //! //! ## Support Ukrainian military forces πŸ‡ΊπŸ‡¦ //! @@ -404,6 +406,9 @@ //! //! Thank you. +// Set `no_std` flag if `std` feature is disabled. +#![cfg_attr(not(feature = "std"), no_std)] + pub use nutype_macros::nutype; #[cfg(test)] diff --git a/nutype_macros/Cargo.toml b/nutype_macros/Cargo.toml index a02404a..65c9019 100644 --- a/nutype_macros/Cargo.toml +++ b/nutype_macros/Cargo.toml @@ -30,6 +30,7 @@ urlencoding = "2.0" proc-macro = true [features] +std = [] serde = [] schemars08 = [] new_unchecked = [] diff --git a/nutype_macros/src/common/gen/error.rs b/nutype_macros/src/common/gen/error.rs index 4ecff62..567084e 100644 --- a/nutype_macros/src/common/gen/error.rs +++ b/nutype_macros/src/common/gen/error.rs @@ -1,3 +1,4 @@ +use cfg_if::cfg_if; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -8,12 +9,21 @@ pub fn gen_error_type_name(type_name: &TypeName) -> ErrorTypeName { ErrorTypeName::new(ident) } +// NOTE: There is no `::core::error::Error` yet in stable Rust. +// So for `no_std` we just don't implement `Error` trait. +#[allow(unused_variables)] pub fn gen_impl_error_trait(error_type_name: &ErrorTypeName) -> TokenStream { - quote! { - impl ::std::error::Error for #error_type_name { - fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { - None + cfg_if! { + if #[cfg(feature = "std")] { + quote! { + impl ::std::error::Error for #error_type_name { + fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { + None + } + } } + } else { + quote!{} } } } diff --git a/nutype_macros/src/common/gen/parse_error.rs b/nutype_macros/src/common/gen/parse_error.rs index cfe9c72..db8acf8 100644 --- a/nutype_macros/src/common/gen/parse_error.rs +++ b/nutype_macros/src/common/gen/parse_error.rs @@ -1,3 +1,4 @@ +use cfg_if::cfg_if; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -55,11 +56,19 @@ pub fn gen_def_parse_error( } }; - let impl_std_error = quote! { - impl ::std::error::Error for #parse_error_type_name { - fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { - None - } + cfg_if! { + if #[cfg(feature = "std")] { + let impl_std_error = quote! { + impl ::std::error::Error for #parse_error_type_name { + fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { + None + } + } + }; + } else { + // NOTE: There is no `::core::error::Error` yet in stable Rust. + // So for `no_std` we just don't implement `Error` trait. + let impl_std_error = quote! {}; } }; diff --git a/nutype_macros/src/float/gen/traits/arbitrary.rs b/nutype_macros/src/float/gen/traits/arbitrary.rs index 4050311..c2525af 100644 --- a/nutype_macros/src/float/gen/traits/arbitrary.rs +++ b/nutype_macros/src/float/gen/traits/arbitrary.rs @@ -373,7 +373,7 @@ fn generate_float_with_condition( for i in 0..1000 { // With every iteration we modify next single byte by adding `i` value to // it. - let index = i % std::mem::size_of::<#inner_type>(); + let index = i % core::mem::size_of::<#inner_type>(); bytes[index] = bytes[index].wrapping_add((i % 256) as u8); // Try to convert the bytes back to float in both BE and NE formats and see