├── .release-please-manifest.json ├── .gitignore ├── Cargo.toml ├── aliri_braid_impl ├── README.md ├── Cargo.toml └── src │ ├── codegen │ ├── check_mode.rs │ ├── symbol.rs │ ├── impls.rs │ ├── mod.rs │ ├── owned.rs │ └── borrowed.rs │ └── lib.rs ├── .rustfmt.toml ├── .editorconfig ├── .github ├── renovate.json └── workflows │ ├── release-please.yml │ ├── release.yml │ └── rust.yml ├── aliri_braid_examples ├── src │ ├── ref_only.rs │ ├── wrapper.rs │ ├── lib.rs │ ├── bytes.rs │ ├── minimal.rs │ ├── validated.rs │ ├── sso_wrapper.rs │ └── normalized.rs └── Cargo.toml ├── release-please-config.json ├── aliri_braid ├── Cargo.toml └── tests │ ├── basic.rs │ ├── common │ ├── mod.rs │ ├── infallible.rs │ ├── fallible.rs │ └── normalized.rs │ ├── normalized.rs │ ├── too_many_braids.rs │ ├── validated.rs │ └── jumble.rs ├── CHANGELOG.md └── README.md /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "aliri_braid", 4 | "aliri_braid_examples", 5 | ] 6 | 7 | # Generate README 8 | # cargo readme -r aliri_braid/ -o ../README.md --no-license 9 | -------------------------------------------------------------------------------- /aliri_braid_impl/README.md: -------------------------------------------------------------------------------- 1 | # aliri_braid_impl 2 | 3 | Implementation macros for [`aliri_braid`], which is probably what 4 | you really want. 5 | 6 | [`aliri_braid`]: https://crates.io/crates/aliri_braid 7 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | max_width = 100 3 | comment_width = 100 4 | wrap_comments = true 5 | format_strings = true 6 | group_imports = "StdExternalCrate" 7 | imports_granularity = "Crate" 8 | imports_layout = "Mixed" 9 | condense_wildcard_suffixes = true 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = true 9 | 10 | [*.rs] 11 | trim_trailing_whitespace = true 12 | 13 | [*.json] 14 | indent_size = 2 15 | 16 | [*.yml] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>neoeinstein/renovate-config", 5 | "github>neoeinstein/renovate-config//rust/library", 6 | "github>neoeinstein/renovate-config:approve-breaking", 7 | ":automergeDigest" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: Release-please 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: google-github-actions/release-please-action@e4dc86ba9405554aeba3c6bb2d169500e7d3b4ee # v4 17 | with: 18 | token: ${{ secrets.RELEASE_PLEASE_PAT }} 19 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/ref_only.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a basic ref-only strongly-typed wrapper around 2 | //! a string slice. 3 | //! 4 | //! The types in this module do not perform any validation or normalization 5 | //! of their values, so every valid UTF-8 string is potentially valid for 6 | //! these types. 7 | 8 | use aliri_braid::braid_ref; 9 | 10 | /// A basic example of a wrapper around a [`str`] 11 | #[braid_ref(serde, no_std)] 12 | pub struct Element; 13 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/wrapper.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a basic strongly-typed wrapper around 2 | //! a string value. 3 | //! 4 | //! The types in this module do not perform any validation or normalization 5 | //! of their values, so every valid UTF-8 string is potentially valid for 6 | //! these types. 7 | 8 | use aliri_braid::braid; 9 | 10 | /// A basic example of a wrapper around a [`String`] 11 | /// 12 | /// This type ends in _Buf_, so the borrowed form of this type 13 | /// will be named [`Username`]. 14 | #[braid( 15 | serde, 16 | ref_doc = "A borrowed reference to a basic string slice wrapper" 17 | )] 18 | pub struct UsernameBuf; 19 | -------------------------------------------------------------------------------- /aliri_braid_impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aliri_braid_impl" 3 | description = "Implementation macros for the `aliri_braid` crate" 4 | version = "0.4.0" 5 | authors = ["Marcus Griep "] 6 | edition = "2018" 7 | readme = "README.md" 8 | license = "MIT OR Apache-2.0" 9 | repository = "https://github.com/neoeinstein/aliri_braid" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.56" 18 | quote = "1.0.26" 19 | syn = { version = "2.0.15", features = ["full"] } 20 | 21 | [dev-dependencies] 22 | pretty_assertions = "1.4.0" 23 | serde = { version = "1", features = [ "derive" ] } 24 | serde_json = "1" 25 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Examples showing the output of using [`aliri_braid`] to 2 | //! generate strongly-typed wrappers around string values. 3 | //! 4 | //! Three types of braids are demonstrated: 5 | //! * [Wrapper][wrapper] 6 | //! * A wrapper around a string with [small-string optimizations][sso_wrapper] 7 | //! * A wrapper around a string [backed by `Bytes`][bytes] 8 | //! * [Validated][validated] 9 | //! * [Normalized][normalized] 10 | //! 11 | //! In addition, the [`minimal`] module demonstrates the minimal string 12 | //! implementation that can be wrapped inside a braid type. 13 | #![deny(unsafe_code)] 14 | 15 | pub mod bytes; 16 | pub mod minimal; 17 | pub mod normalized; 18 | pub mod ref_only; 19 | pub mod sso_wrapper; 20 | pub mod validated; 21 | pub mod wrapper; 22 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "release-type": "rust", 4 | "bump-minor-pre-major": true, 5 | "bump-patch-for-minor-pre-major": true, 6 | "include-v-in-tag": true, 7 | "include-component-in-tag": false, 8 | "last-release-sha": "c53cc5d7b5e6830e2b0eafa4459ce71953b2a1d1", 9 | "plugins": [ 10 | { 11 | "type": "cargo-workspace", 12 | "merge": false 13 | }, { 14 | "type": "linked-versions", 15 | "groupName": "aliri_braid", 16 | "components": [ 17 | "aliri_braid", 18 | "aliri_braid_examples", 19 | "aliri_braid_impl" 20 | ] 21 | }], 22 | "packages": { 23 | "aliri_braid": {}, 24 | "aliri_braid_examples": {}, 25 | "aliri_braid_impl": {} 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/bytes.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a basic strongly-typed wrapper around 2 | //! a [`Bytes`]-backed value. 3 | //! 4 | //! The types in this module do not perform any validation or normalization 5 | //! of their values, so every valid UTF-8 string is potentially valid for 6 | //! these types. 7 | //! 8 | //! [`Bytes`]: https://docs.rs/bytes/*/bytes/struct.Bytes.html 9 | 10 | use aliri_braid::braid; 11 | use bytestring::ByteString; 12 | 13 | /// A basic example of a wrapper around a [`Bytes`] 14 | /// 15 | /// This type ends in _Buf_, so the borrowed form of this type 16 | /// will be named [`Username`]. 17 | /// 18 | /// [`Bytes`]: https://docs.rs/bytes/*/bytes/struct.Bytes.html 19 | #[braid( 20 | serde, 21 | ref_doc = "A borrowed reference to a basic string slice wrapper" 22 | )] 23 | pub struct UsernameBuf(ByteString); 24 | -------------------------------------------------------------------------------- /aliri_braid_examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aliri_braid_examples" 3 | description = "Examples demonstrating usage of the `aliri_braid` crate" 4 | keywords = [ "string", "newtype", "validation", "normalization" ] 5 | categories = [ "data-structures", "rust-patterns" ] 6 | version = "0.4.0" 7 | authors = ["Marcus Griep "] 8 | edition = "2018" 9 | readme = "../README.md" 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/neoeinstein/aliri_braid" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | aliri_braid = { version = "0.4.0", path = "../aliri_braid" } 17 | bytes = "1" 18 | bytestring = { version = "1.1", features = [ "serde" ] } 19 | serde = { version = "1", features = [ "derive" ] } 20 | smartstring = { version = "1", features = [ "serde" ] } 21 | compact_str = { version = "0.7", features = [ "serde" ] } 22 | -------------------------------------------------------------------------------- /aliri_braid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aliri_braid" 3 | description = "Improve and strengthen your strings by making them strongly-typed with less boilerplate" 4 | keywords = [ "string", "newtype", "validation", "normalization" ] 5 | categories = [ "data-structures", "rust-patterns" ] 6 | version = "0.4.0" 7 | authors = ["Marcus Griep "] 8 | edition = "2018" 9 | readme = "../README.md" 10 | license = "MIT OR Apache-2.0" 11 | repository = "https://github.com/neoeinstein/aliri_braid" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | default = ["alloc"] 17 | alloc = [] 18 | 19 | [dependencies] 20 | aliri_braid_impl = { version = "=0.4.0", path = "../aliri_braid_impl" } 21 | 22 | [dev-dependencies] 23 | bytes = "1" 24 | bytestring = "1.3" 25 | compact_str = "0.7" 26 | quickcheck = "1" 27 | quickcheck_macros = "1.0.0" 28 | serde = { version = "1", features = [ "derive" ] } 29 | serde_json = "1" 30 | smartstring = "1" 31 | static_assertions = "1" 32 | 33 | [package.metadata.docs.rs] 34 | rustdoc-args = ["--cfg", "docsrs"] 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]*.[0-9]*.[0-9]*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | release: 13 | name: Publish Release ${{ github.ref_name }} 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 17 | - name: Install Stable Rust Toolchain 18 | uses: dtolnay/rust-toolchain@stable 19 | - id: parse-tag 20 | name: Verify tag matches cargo manifest 21 | run: | 22 | VERSION=$(cargo read-manifest --manifest-path aliri_braid/Cargo.toml | jq -r .version) 23 | if [ "${{ github.ref_name }}" != "v${VERSION}" ]; then 24 | echo "::error::Tag name ${{ github.ref_name }} doesn't match the version in Cargo.toml (${VERSION})"; 25 | exit 1; 26 | fi 27 | shell: bash 28 | - name: Publish Rust crate 29 | env: 30 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 31 | CARGO_PUBLISH_TIMEOUT: '0' 32 | run: | 33 | cargo publish -p aliri_braid_impl 34 | cargo publish -p aliri_braid 35 | cargo publish -p aliri_braid_examples 36 | shell: bash 37 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/minimal.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a basic strongly-typed wrapper around 2 | //! a custom string-like type. 3 | 4 | use aliri_braid::braid; 5 | 6 | /// A wrapper around a custom string-like type that implements the 7 | /// minimal set of required traits for a braid type 8 | #[braid(serde)] 9 | pub struct MinimalUsernameBuf(MinimalString); 10 | 11 | /// An example of a minimal string implementaiton that can be wrapped inside 12 | /// an owned braid type. 13 | #[derive( 14 | Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, 15 | )] 16 | pub struct MinimalString(String); 17 | 18 | impl From for MinimalString { 19 | fn from(s: String) -> Self { 20 | MinimalString(s) 21 | } 22 | } 23 | 24 | impl From<&'_ str> for MinimalString { 25 | fn from(s: &str) -> Self { 26 | MinimalString(s.into()) 27 | } 28 | } 29 | 30 | impl From> for MinimalString { 31 | fn from(s: Box) -> Self { 32 | MinimalString(s.into()) 33 | } 34 | } 35 | 36 | impl AsRef for MinimalString { 37 | fn as_ref(&self) -> &str { 38 | self.0.as_ref() 39 | } 40 | } 41 | 42 | impl From for String { 43 | fn from(s: MinimalString) -> Self { 44 | s.0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format of this changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.4.0] - 2023-05-26 8 | 9 | - BREAKING: Providing a custom override for the borrowed form name has changed from `ref` to 10 | `ref_name`, as `syn` now requires that it be a valid path (and `ref` is a keyword) 11 | - Upgraded `syn` dependency to v2 12 | 13 | ## [0.3.1] - 2022-11-02 14 | 15 | ### Fixed 16 | 17 | - Normalized braids with custom inner types would cause compiler errors on trying to convert from 18 | `Cow`. 19 | 20 | ## [0.3.0] - 2022-11-02 21 | 22 | ### Added 23 | 24 | - `from_infallible!()` utility macro added for providing universal `From` impls 25 | - Added an example using `CompactString` from the `compact_str` crate ([#15]) 26 | 27 | ### Changed 28 | 29 | - `String` at the end of a braid name will now be shortened to `Str` in the borrowed form ([#19]) 30 | - `Validator::Error` is now expected to implement `From` to allow potentially 31 | fallible wrapped type conversions 32 | 33 | [#15]: https://github.com/neoeinstein/aliri_braid/pull/15 34 | [#19]: https://github.com/neoeinstein/aliri_braid/pull/19 35 | 36 | ## [0.2.4] - 2022-07-21 37 | 38 | ### Fixed 39 | 40 | - Removed unnecessary lifetime annotations that cause clippy warnings in 1.62 ([#20]) 41 | 42 | [#20]: https://github.com/neoeinstein/aliri_braid/pull/20 43 | 44 | ## [0.2.3] - 2022-06-15 45 | 46 | ### Changed 47 | 48 | - Added allow for clippy lint on automatically derived `serde::Deserialize` impls 49 | 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - staging 8 | - trying 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | CARGO_UNSTABLE_SPARSE_REGISTRY: true 16 | 17 | jobs: 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 23 | - uses: dtolnay/rust-toolchain@nightly 24 | with: 25 | components: rustfmt 26 | - run: cargo +nightly fmt --all -- --check 27 | - uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: clippy 30 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2 31 | - run: cargo clippy -- -D warnings 32 | 33 | test: 34 | name: Test 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 38 | - uses: dtolnay/rust-toolchain@stable 39 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2 40 | - name: Install nextest 41 | uses: taiki-e/install-action@nextest 42 | - run: cargo nextest run --no-fail-fast 43 | - run: cargo test --doc 44 | 45 | miri: 46 | name: Miri 47 | runs-on: ubuntu-latest 48 | steps: 49 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6 50 | - uses: dtolnay/rust-toolchain@miri 51 | - uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2 52 | - run: cargo miri setup 53 | - run: cargo miri test --no-fail-fast 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aliri_braid 2 | 3 | [![Build Status](https://github.com/neoeinstein/aliri_braid/actions/workflows/rust.yml/badge.svg?branch=main&event=push)](https://github.com/neoeinstein/aliri_braid) 4 | 5 | Improve and strengthen your strings 6 | 7 | Strongly-typed APIs reduce errors and confusion over passing around un-typed strings. 8 | Braid helps in that endeavor by making it painless to create wrappers around your 9 | string values, ensuring that you use them in the right way every time. 10 | 11 | Examples of the documentation and implementations provided for braids are available 12 | below and in the [`aliri_braid_examples`] crate documentation. 13 | 14 | [`aliri_braid_examples`]: https://docs.rs/aliri_braid_examples/ 15 | 16 | ## Usage 17 | 18 | A braid is created by attaching `#[braid]` to a struct definition. The macro will take 19 | care of automatically updating the representation of the struct to wrap a string and 20 | generate the borrowed form of the strong type. 21 | 22 | ```rust 23 | use aliri_braid::braid; 24 | 25 | #[braid] 26 | pub struct DatabaseName; 27 | ``` 28 | 29 | Braids of custom string types are also supported, so long as they implement a set of 30 | expected traits. If not specified, the type named `String` in the current namespace 31 | will be used. 32 | 33 | ```rust 34 | use aliri_braid::braid; 35 | use compact_str::CompactString as String; 36 | 37 | #[braid] 38 | pub struct UserId; 39 | ``` 40 | 41 | Once created, braids can be passed around as strongly-typed, immutable strings. 42 | 43 | ```rust 44 | fn take_strong_string(n: DatabaseName) {} 45 | fn borrow_strong_string(n: &DatabaseNameRef) {} 46 | 47 | let owned = DatabaseName::new(String::from("mongo")); 48 | borrow_strong_string(&owned); 49 | take_strong_string(owned); 50 | ``` 51 | 52 | A braid can also be untyped for use in stringly-typed interfaces. 53 | 54 | ```rust 55 | fn take_raw_string(s: String) {} 56 | fn borrow_raw_str(s: &str) {} 57 | 58 | let owned = DatabaseName::new(String::from("mongo")); 59 | borrow_raw_str(owned.as_str()); 60 | take_raw_string(owned.take()); 61 | ``` 62 | 63 | For more information, see the [documentation on docs.rs](https://docs.rs/aliri_braid). 64 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/validated.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a strongly-typed wrapper around 2 | //! a validated string value. 3 | //! 4 | //! The types in this module perform validation prior to allowing the 5 | //! type to be instantiated. If a value is invalid, then an error will 6 | //! be returned rather than allowing the invalid value to be constructed. 7 | //! 8 | //! Refer to the [`Validator`][aliri_braid::Validator] implementation 9 | //! for a given type for additional information on what is considered 10 | //! a valid value for the type. 11 | 12 | use std::{convert::Infallible, error, fmt}; 13 | 14 | use aliri_braid::braid; 15 | 16 | /// An error indicating that the provided string is not a valid scope token 17 | #[derive(Debug)] 18 | pub enum InvalidScopeToken { 19 | /// A scope token cannot be the empty string 20 | EmptyString, 21 | /// The string contained a byte that is not legal for a scope token 22 | InvalidCharacter { position: usize, value: u8 }, 23 | } 24 | 25 | impl fmt::Display for InvalidScopeToken { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | match self { 28 | Self::EmptyString => f.write_str("scope cannot be empty"), 29 | Self::InvalidCharacter { position, value } => f.write_fmt(format_args!( 30 | "invalid scope character at position {}: {:02x}", 31 | position, value 32 | )), 33 | } 34 | } 35 | } 36 | 37 | impl From for InvalidScopeToken { 38 | #[inline(always)] 39 | fn from(x: Infallible) -> Self { 40 | match x {} 41 | } 42 | } 43 | 44 | impl error::Error for InvalidScopeToken {} 45 | 46 | /// A scope token as defined in RFC6749, Section 3.3 47 | /// 48 | /// This type maintains an invariant that ensures that a 49 | /// value of this type cannot be constructed that contains 50 | /// invalid data. 51 | /// 52 | /// The borrowed form of this type is generated by appending 53 | /// _Ref_ to the end of the owned form: [`ScopeTokenRef`]. 54 | #[braid(serde, validator, ref_doc = "A borrowed reference to a [`ScopeToken`]")] 55 | pub struct ScopeToken; 56 | 57 | impl aliri_braid::Validator for ScopeToken { 58 | type Error = InvalidScopeToken; 59 | 60 | fn validate(s: &str) -> Result<(), Self::Error> { 61 | if s.is_empty() { 62 | Err(InvalidScopeToken::EmptyString) 63 | } else if let Some((position, &value)) = s 64 | .as_bytes() 65 | .iter() 66 | .enumerate() 67 | .find(|(_, &b)| b <= 0x20 || b == 0x22 || b == 0x5C || 0x7F <= b) 68 | { 69 | Err(InvalidScopeToken::InvalidCharacter { position, value }) 70 | } else { 71 | Ok(()) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /aliri_braid/tests/basic.rs: -------------------------------------------------------------------------------- 1 | use aliri_braid::braid; 2 | 3 | /// A basic example of a wrapper around a [`String`] 4 | #[braid( 5 | serde, 6 | ref_doc = "A borrowed reference to a basic string slice wrapper" 7 | )] 8 | pub struct BasicExampleBuf; 9 | 10 | #[cfg(test)] 11 | mod tests { 12 | use std::borrow::Borrow; 13 | 14 | use super::*; 15 | 16 | #[test] 17 | fn constant_ref_works() { 18 | const TEST_CONSTANT: &BasicExample = BasicExample::from_static("test"); 19 | assert_eq!(TEST_CONSTANT.as_str(), "test"); 20 | } 21 | 22 | #[test] 23 | fn owned_works() { 24 | let x = BasicExampleBuf::from_static("Testing the Buffer"); 25 | assert_eq!(x.as_str(), "Testing the Buffer"); 26 | } 27 | 28 | #[test] 29 | fn borrowing_implicit() { 30 | let x: &BasicExample = &BasicExampleBuf::from_static("Testing the Buffer"); 31 | assert_eq!(x.as_str(), "Testing the Buffer"); 32 | } 33 | 34 | #[test] 35 | fn ref_works() { 36 | let x = BasicExample::from_str("Testing the Reference"); 37 | assert_eq!(x.as_str(), "Testing the Reference"); 38 | } 39 | 40 | #[allow(dead_code)] 41 | struct Bar<'a> { 42 | foo: std::borrow::Cow<'a, BasicExample>, 43 | } 44 | 45 | #[test] 46 | fn owned_as_cow() { 47 | let owned = BasicExampleBuf::from_static("Testing the Buffer"); 48 | let _bar = Bar { foo: owned.into() }; 49 | } 50 | 51 | #[test] 52 | fn borrowed_as_cow() { 53 | let borrowed = BasicExample::from_str("Testing the Reference"); 54 | let _bar = Bar { 55 | foo: borrowed.into(), 56 | }; 57 | } 58 | 59 | #[test] 60 | fn owned_as_ref_borrowed() { 61 | let owned = BasicExampleBuf::from_static("Testing the Buffer"); 62 | let _reference: &BasicExample = owned.as_ref(); 63 | } 64 | 65 | #[test] 66 | fn owned_as_ref_str() { 67 | let owned = BasicExampleBuf::from_static("Testing the Buffer"); 68 | let _reference: &str = owned.as_ref(); 69 | } 70 | 71 | #[test] 72 | fn borrowed_as_ref_str() { 73 | let owned = BasicExample::from_str("Testing the Buffer"); 74 | let _reference: &str = owned.as_ref(); 75 | } 76 | 77 | #[test] 78 | fn owned_borrow_borrowed() { 79 | let owned = BasicExampleBuf::from_static("Testing the Buffer"); 80 | let _reference: &BasicExample = owned.borrow(); 81 | } 82 | 83 | #[test] 84 | fn owned_borrow_str() { 85 | let owned = BasicExampleBuf::from_static("Testing the Buffer"); 86 | let _reference: &str = owned.borrow(); 87 | } 88 | 89 | #[test] 90 | fn borrowed_borrow_str() { 91 | let owned = BasicExample::from_str("Testing the Buffer"); 92 | let _reference: &str = owned.borrow(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/check_mode.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | 3 | pub const VALIDATOR: &str = "validator"; 4 | pub const NORMALIZER: &str = "normalizer"; 5 | 6 | pub enum CheckMode { 7 | None, 8 | Validate(syn::Type), 9 | Normalize(syn::Type), 10 | } 11 | 12 | impl Default for CheckMode { 13 | fn default() -> Self { 14 | Self::None 15 | } 16 | } 17 | 18 | impl CheckMode { 19 | pub fn serde_err_handler(&self) -> Option { 20 | match self { 21 | Self::None => None, 22 | _ => Some(quote::quote! {.map_err(::custom)?}), 23 | } 24 | } 25 | } 26 | 27 | #[derive(Clone)] 28 | pub enum IndefiniteCheckMode { 29 | None, 30 | Validate(Option), 31 | Normalize(Option), 32 | } 33 | 34 | impl Default for IndefiniteCheckMode { 35 | fn default() -> Self { 36 | Self::None 37 | } 38 | } 39 | 40 | impl IndefiniteCheckMode { 41 | pub fn try_set_validator(&mut self, validator: Option) -> Result<(), String> { 42 | if matches!(self, Self::None) { 43 | *self = Self::Validate(validator); 44 | return Ok(()); 45 | } 46 | 47 | let err_desc = if matches!(self, Self::Validate(_)) { 48 | format!("{} can only be specified once", VALIDATOR) 49 | } else { 50 | format!( 51 | "only one of {} and {} can be specified at a time", 52 | VALIDATOR, NORMALIZER, 53 | ) 54 | }; 55 | 56 | Err(err_desc) 57 | } 58 | 59 | pub fn try_set_normalizer(&mut self, normalizer: Option) -> Result<(), String> { 60 | if matches!(self, Self::None) { 61 | *self = Self::Normalize(normalizer); 62 | return Ok(()); 63 | } 64 | 65 | let err_desc = if matches!(self, Self::Normalize(_)) { 66 | format!("{} can only be specified once", NORMALIZER) 67 | } else { 68 | format!( 69 | "only one of {} and {} can be specified at a time", 70 | VALIDATOR, NORMALIZER, 71 | ) 72 | }; 73 | 74 | Err(err_desc) 75 | } 76 | 77 | pub fn infer_validator_if_missing(self, default: &syn::Ident) -> CheckMode { 78 | match self { 79 | Self::None => CheckMode::None, 80 | Self::Validate(Some(validator)) => CheckMode::Validate(validator), 81 | Self::Validate(None) => CheckMode::Validate(ident_to_type(default)), 82 | Self::Normalize(Some(normalizer)) => CheckMode::Normalize(normalizer), 83 | Self::Normalize(None) => CheckMode::Normalize(ident_to_type(default)), 84 | } 85 | } 86 | } 87 | 88 | pub fn ident_to_type(ident: &syn::Ident) -> syn::Type { 89 | let tokens = ident.to_token_stream(); 90 | 91 | syn::parse_quote!(#tokens) 92 | } 93 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/sso_wrapper.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a strongly-typed wrapper around 2 | //! a string with small-string optimization. 3 | //! 4 | //! The types in this module do not perform any validation or normalization 5 | //! of their values, so every valid UTF-8 string is potentially valid for 6 | //! these types. 7 | 8 | use std::{borrow::Cow, error, fmt}; 9 | 10 | use aliri_braid::braid; 11 | use smartstring::alias::String; 12 | 13 | /// An example of a wrapper around a [`smartstring::SmartString`] with 14 | /// small-string optimization 15 | /// 16 | /// This type ends in _Buf_, so the borrowed form of this type 17 | /// will be named [`SmartUsername`]. 18 | /// 19 | /// Because the no type is explicitly named here, the inner field will 20 | /// implicitly use the `String` type in the namespace where it is defined. 21 | #[braid(serde, ref_doc = "A borrowed reference to a string slice wrapper")] 22 | pub struct SmartUsernameBuf; 23 | 24 | /// An example of a wrapper with small-string optimization 25 | /// 26 | /// This type wraps the around a [`compact_str::CompactString`], but that 27 | /// implementation detail won't be exposed through the type API due to 28 | /// the use of the `no_expose` braid parameter. 29 | #[braid(serde, no_expose)] 30 | pub struct CompactData(compact_str::CompactString); 31 | 32 | /// A non-empty [`String`] normalized to lowercase with small-string optimization 33 | /// 34 | /// This type maintains an invariant that ensures that a 35 | /// value of this type cannot be constructed that contains 36 | /// invalid data. Data that _can_ be normalized to a valid 37 | /// instance of this type will be. 38 | /// 39 | /// Because this type does normalization, the type explicitly 40 | /// does _not_ implement [`Borrow`][::std::borrow::Borrow], 41 | /// as doing so would could violate the contract of that trait, 42 | /// potentially resulting in lost data. If a user of 43 | /// the crate would like to override this, then they can 44 | /// explicitly implement the trait. 45 | #[braid( 46 | serde, 47 | no_expose, 48 | normalizer, 49 | ref_doc = "A borrowed reference to a non-empty, lowercase string" 50 | )] 51 | pub struct LowerCompactString(compact_str::CompactString); 52 | 53 | impl aliri_braid::Validator for LowerCompactString { 54 | type Error = InvalidString; 55 | 56 | fn validate(raw: &str) -> Result<(), Self::Error> { 57 | if raw.is_empty() { 58 | Err(InvalidString::EmptyString) 59 | } else if raw.chars().any(char::is_uppercase) { 60 | Err(InvalidString::InvalidCharacter) 61 | } else { 62 | Ok(()) 63 | } 64 | } 65 | } 66 | 67 | impl aliri_braid::Normalizer for LowerCompactString { 68 | fn normalize(s: &str) -> Result, Self::Error> { 69 | if s.is_empty() { 70 | Err(InvalidString::EmptyString) 71 | } else if s.contains(char::is_uppercase) { 72 | Ok(Cow::Owned(s.to_lowercase())) 73 | } else { 74 | Ok(Cow::Borrowed(s)) 75 | } 76 | } 77 | } 78 | 79 | /// An error indicating that the provided value was invalid 80 | #[derive(Debug)] 81 | pub enum InvalidString { 82 | EmptyString, 83 | InvalidCharacter, 84 | } 85 | 86 | impl fmt::Display for InvalidString { 87 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 88 | match self { 89 | Self::EmptyString => f.write_str("string cannot be empty"), 90 | Self::InvalidCharacter => f.write_str("string contains invalid uppercase character"), 91 | } 92 | } 93 | } 94 | 95 | impl error::Error for InvalidString {} 96 | aliri_braid::from_infallible!(InvalidString); 97 | -------------------------------------------------------------------------------- /aliri_braid/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! assert_impl_all_with_lifetime { 2 | ($type:ty: $($trait:path),+ $(,)?) => { 3 | const _: fn() = || { 4 | // Only callable when `$type` implements all traits in `$($trait)+`. 5 | fn assert_impl_all<'a, 'b: 'a, T: ?Sized $(+ $trait)+>() {} 6 | assert_impl_all::<$type>(); 7 | }; 8 | }; 9 | } 10 | 11 | macro_rules! assert_core_impls { 12 | ($owned:ty => $borrowed:ty) => { 13 | assert_impl_all_with_lifetime!( 14 | $owned: 15 | std::convert::From, 16 | std::convert::From<&'a str>, 17 | std::borrow::Borrow, 18 | ); 19 | 20 | assert_impl_all_with_lifetime!( 21 | $borrowed: 22 | std::borrow::Borrow, 23 | ); 24 | 25 | assert_impl_all_with_lifetime!( 26 | &$borrowed: 27 | std::convert::From<&'a str>, 28 | ); 29 | 30 | assert_core_impls!($owned => $borrowed where ValidationError = std::convert::Infallible); 31 | }; 32 | ($owned:ty => $borrowed:ty where NormalizationError = $error:ty, ValidationError = $verror:ty) => { 33 | assert_core_impls!($owned => $borrowed where Error = ($error, $verror)); 34 | }; 35 | ($owned:ty => $borrowed:ty where ValidationError = $error:ty) => { 36 | assert_impl_all_with_lifetime!( 37 | $owned: 38 | std::borrow::Borrow, 39 | ); 40 | 41 | assert_impl_all_with_lifetime!( 42 | $borrowed: 43 | std::borrow::Borrow, 44 | ); 45 | 46 | assert_core_impls!($owned => $borrowed where Error = ($error, $error)); 47 | }; 48 | ($owned:ty => $borrowed:ty where Error = ($error:ty, $verror:ty)) => { 49 | assert_impl_all_with_lifetime!( 50 | $owned: 51 | std::clone::Clone, 52 | std::fmt::Debug, 53 | std::fmt::Display, 54 | std::hash::Hash, 55 | std::cmp::Eq, 56 | std::cmp::Ord, 57 | std::cmp::PartialEq, 58 | std::cmp::PartialEq<$borrowed>, 59 | std::cmp::PartialEq<&'a $borrowed>, 60 | std::cmp::PartialOrd, 61 | std::convert::AsRef<$borrowed>, 62 | std::convert::AsRef, 63 | std::convert::From<&'a $borrowed>, 64 | std::convert::From>, 65 | std::convert::From>, 66 | std::convert::TryFrom, 67 | std::convert::TryFrom<&'a str, Error = $error>, 68 | std::borrow::Borrow<$borrowed>, 69 | std::str::FromStr, 70 | std::ops::Deref, 71 | ); 72 | 73 | assert_impl_all_with_lifetime!( 74 | $borrowed: 75 | std::fmt::Debug, 76 | std::fmt::Display, 77 | std::hash::Hash, 78 | std::cmp::Eq, 79 | std::cmp::Ord, 80 | std::cmp::PartialEq, 81 | std::cmp::PartialEq<$owned>, 82 | std::cmp::PartialOrd, 83 | std::borrow::ToOwned, 84 | ); 85 | 86 | assert_impl_all_with_lifetime!( 87 | &$borrowed: 88 | std::fmt::Debug, 89 | std::fmt::Display, 90 | std::hash::Hash, 91 | std::cmp::Eq, 92 | std::cmp::Ord, 93 | std::cmp::PartialEq, 94 | std::cmp::PartialEq<$owned>, 95 | std::cmp::PartialOrd, 96 | std::convert::From<&'a std::borrow::Cow<'b, $borrowed>>, 97 | std::convert::TryFrom<&'a str, Error = $verror>, 98 | ); 99 | 100 | assert_impl_all_with_lifetime!( 101 | std::borrow::Cow<'static, $borrowed>: 102 | std::convert::From<$owned>, 103 | ); 104 | 105 | assert_impl_all_with_lifetime!( 106 | std::borrow::Cow<$borrowed>: 107 | std::convert::From<&'a $borrowed>, 108 | ); 109 | 110 | static_assertions::assert_impl_all!( 111 | Box<$borrowed>: 112 | std::convert::From<$owned>, 113 | ); 114 | }; 115 | } 116 | 117 | mod fallible; 118 | mod infallible; 119 | mod normalized; 120 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/symbol.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | 3 | use syn::{Ident, Path}; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 6 | pub struct Symbol(&'static str); 7 | 8 | // pub const NO_AUTO_REF: Symbol = Symbol("no_auto_ref"); 9 | // pub const OWNED: Symbol = Symbol("owned"); 10 | pub const CLONE: Symbol = Symbol("clone"); 11 | pub const DEBUG: Symbol = Symbol("debug"); 12 | pub const DISPLAY: Symbol = Symbol("display"); 13 | pub const ORD: Symbol = Symbol("ord"); 14 | pub const SERDE: Symbol = Symbol("serde"); 15 | pub const REF: Symbol = Symbol("ref_name"); 16 | pub const REF_DOC: Symbol = Symbol("ref_doc"); 17 | pub const REF_ATTR: Symbol = Symbol("ref_attr"); 18 | pub const OWNED_ATTR: Symbol = Symbol("owned_attr"); 19 | pub const NO_STD: Symbol = Symbol("no_std"); 20 | pub const NO_EXPOSE: Symbol = Symbol("no_expose"); 21 | pub const VALIDATOR: Symbol = Symbol(super::check_mode::VALIDATOR); 22 | pub const NORMALIZER: Symbol = Symbol(super::check_mode::NORMALIZER); 23 | 24 | impl PartialEq for Ident { 25 | fn eq(&self, word: &Symbol) -> bool { 26 | self == word.0 27 | } 28 | } 29 | 30 | impl<'a> PartialEq for &'a Ident { 31 | fn eq(&self, word: &Symbol) -> bool { 32 | *self == word.0 33 | } 34 | } 35 | 36 | impl PartialEq for Path { 37 | fn eq(&self, word: &Symbol) -> bool { 38 | self.is_ident(word.0) 39 | } 40 | } 41 | 42 | impl<'a> PartialEq for &'a Path { 43 | fn eq(&self, word: &Symbol) -> bool { 44 | self.is_ident(word.0) 45 | } 46 | } 47 | 48 | impl Display for Symbol { 49 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 50 | formatter.write_str(self.0) 51 | } 52 | } 53 | 54 | fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> Result<&syn::LitStr, syn::Error> { 55 | if let syn::Lit::Str(lit) = lit { 56 | Ok(lit) 57 | } else { 58 | Err(syn::Error::new_spanned( 59 | lit, 60 | format!( 61 | "expected attribute `{}` to have a string value (`{} = \"value\"`)", 62 | attr_name, attr_name 63 | ), 64 | )) 65 | } 66 | } 67 | 68 | // fn parse_lit_into_path(attr_name: Symbol, lit: &syn::Lit) -> Result { 69 | // let string = get_lit_str( attr_name, lit)?; 70 | // parse_lit_str(string).map_err(|_| { 71 | // syn::Error::new_spanned(lit, format!("failed to parse path: {:?}", string.value())) 72 | // }) 73 | // } 74 | 75 | pub(super) fn parse_expr_as_lit(expr: &syn::Expr) -> Result<&syn::Lit, syn::Error> { 76 | if let syn::Expr::Lit(l) = expr { 77 | Ok(&l.lit) 78 | } else { 79 | Err(syn::Error::new_spanned( 80 | expr, 81 | "expected a literal in this position", 82 | )) 83 | } 84 | } 85 | 86 | pub(super) fn parse_lit_into_type( 87 | attr_name: Symbol, 88 | lit: &syn::Lit, 89 | ) -> Result { 90 | let string = get_lit_str(attr_name, lit)?; 91 | parse_lit_str(string).map_err(|_| { 92 | syn::Error::new_spanned(lit, format!("failed to parse type: {:?}", string.value())) 93 | }) 94 | } 95 | 96 | pub(super) fn parse_lit_into_string( 97 | attr_name: Symbol, 98 | lit: &syn::Lit, 99 | ) -> Result { 100 | let string = get_lit_str(attr_name, lit)?; 101 | Ok(string.value()) 102 | } 103 | 104 | fn parse_lit_str(s: &syn::LitStr) -> syn::parse::Result 105 | where 106 | T: syn::parse::Parse, 107 | { 108 | let tokens = spanned_tokens(s)?; 109 | syn::parse2(tokens) 110 | } 111 | 112 | fn spanned_tokens(s: &syn::LitStr) -> syn::parse::Result { 113 | let stream = syn::parse_str(&s.value())?; 114 | Ok(respan_token_stream(stream, s.span())) 115 | } 116 | 117 | fn respan_token_stream( 118 | stream: proc_macro2::TokenStream, 119 | span: proc_macro2::Span, 120 | ) -> proc_macro2::TokenStream { 121 | stream 122 | .into_iter() 123 | .map(|token| respan_token_tree(token, span)) 124 | .collect() 125 | } 126 | 127 | fn respan_token_tree( 128 | mut token: proc_macro2::TokenTree, 129 | span: proc_macro2::Span, 130 | ) -> proc_macro2::TokenTree { 131 | if let proc_macro2::TokenTree::Group(g) = &mut token { 132 | *g = proc_macro2::Group::new(g.delimiter(), respan_token_stream(g.stream(), span)); 133 | } 134 | token.set_span(span); 135 | token 136 | } 137 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! You probably want the [`aliri_braid`] crate, which 2 | //! has the documentation this crate lacks. 3 | //! 4 | //! [`aliri_braid`]: https://docs.rs/aliri_braid/*/aliri_braid/ 5 | 6 | #![warn( 7 | missing_docs, 8 | unused_import_braces, 9 | unused_imports, 10 | unused_qualifications 11 | )] 12 | #![deny( 13 | missing_debug_implementations, 14 | trivial_casts, 15 | trivial_numeric_casts, 16 | unused_must_use 17 | )] 18 | #![forbid(unsafe_code)] 19 | 20 | extern crate proc_macro; 21 | 22 | mod codegen; 23 | 24 | use codegen::{Params, ParamsRef}; 25 | use proc_macro::TokenStream; 26 | use syn::parse_macro_input; 27 | 28 | /// Constructs a braid 29 | /// 30 | /// Any attributes assigned to the the struct will be applied to both the owned 31 | /// and borrowed types, except for doc-comments, with will only be applied to the 32 | /// owned form. 33 | /// 34 | /// Available options: 35 | /// * `ref_name = "RefName"` 36 | /// * Sets the name of the borrowed type 37 | /// * `ref_doc = "Alternate doc comment"` 38 | /// * Overrides the default doc comment for the borrowed type 39 | /// * `ref_attr = "#[derive(...)]"` 40 | /// * Provides an attribute to be placed only on the borrowed type 41 | /// * `owned_attr = "#[derive(...)]"` 42 | /// * Provides an attribute to be placed only on the owned type 43 | /// * either `validator [ = "Type" ]` or `normalizer [ = "Type" ]` 44 | /// * Indicates the type is validated or normalized. If not specified, it is assumed that the 45 | /// braid implements the relevant trait itself. 46 | /// * `clone = "impl|omit"` (default: `impl`) 47 | /// * Changes the automatic derivation of a `Clone` implementation on the owned type. 48 | /// * `debug = "impl|owned|omit"` (default `impl`) 49 | /// * Changes how automatic implementations of the `Debug` trait are provided. If `owned`, then 50 | /// the owned type will generate a `Debug` implementation that will just delegate to the 51 | /// borrowed implementation. If `omit`, then no implementations of `Debug` will be provided. 52 | /// * `display = "impl|owned|omit"` (default `impl`) 53 | /// * Changes how automatic implementations of the `Display` trait are provided. If `owned`, then 54 | /// the owned type will generate a `Display` implementation that will just delegate to the 55 | /// borrowed implementation. If `omit`, then no implementations of `Display` will be provided. 56 | /// * `ord = "impl|owned|omit"` (default `impl`) 57 | /// * Changes how automatic implementations of the `PartialOrd` and `Ord` traits are provided. If 58 | /// `owned`, then the owned type will generate implementations that will just delegate to the 59 | /// borrowed implementations. If `omit`, then no implementations will be provided. 60 | /// * `serde = "impl|omit"` (default `omit`) 61 | /// * Adds serialize and deserialize implementations 62 | /// * `no_expose` 63 | /// * Functions that expose the internal field type will not be exposed publicly. 64 | /// * `no_std` 65 | /// * Generates `no_std`-compatible braid (still requires `alloc`) 66 | #[proc_macro_attribute] 67 | pub fn braid(args: TokenStream, input: TokenStream) -> TokenStream { 68 | let args = parse_macro_input!(args as Params); 69 | let body = parse_macro_input!(input as syn::ItemStruct); 70 | 71 | args.build(body) 72 | .map_or_else(syn::Error::into_compile_error, |codegen| codegen.generate()) 73 | .into() 74 | } 75 | 76 | /// Constructs a ref-only braid 77 | /// 78 | /// Available options: 79 | /// * either `validator [ = "Type" ]` 80 | /// * Indicates the type is validated. If not specified, it is assumed that the braid implements 81 | /// the relevant trait itself. 82 | /// * `debug = "impl|omit"` (default `impl`) 83 | /// * Changes how automatic implementations of the `Debug` trait are provided. If `omit`, then no 84 | /// implementations of `Debug` will be provided. 85 | /// * `display = "impl|omit"` (default `impl`) 86 | /// * Changes how automatic implementations of the `Display` trait are provided. If `omit`, then 87 | /// no implementations of `Display` will be provided. 88 | /// * `ord = "impl|omit"` (default `impl`) 89 | /// * Changes how automatic implementations of the `PartialOrd` and `Ord` traits are provided. If 90 | /// `omit`, then no implementations will be provided. 91 | /// * `serde = "impl|omit"` (default `omit`) 92 | /// * Adds serialize and deserialize implementations 93 | /// * `no_std` 94 | /// * Generates a `no_std`-compatible braid that doesn't require `alloc` 95 | #[proc_macro_attribute] 96 | pub fn braid_ref(args: TokenStream, input: TokenStream) -> TokenStream { 97 | let args = parse_macro_input!(args as ParamsRef); 98 | let mut body = parse_macro_input!(input as syn::ItemStruct); 99 | 100 | args.build(&mut body) 101 | .unwrap_or_else(syn::Error::into_compile_error) 102 | .into() 103 | } 104 | 105 | fn as_validator(validator: &syn::Type) -> proc_macro2::TokenStream { 106 | quote::quote! { <#validator as ::aliri_braid::Validator> } 107 | } 108 | 109 | fn as_normalizer(normalizer: &syn::Type) -> proc_macro2::TokenStream { 110 | quote::quote! { <#normalizer as ::aliri_braid::Normalizer> } 111 | } 112 | -------------------------------------------------------------------------------- /aliri_braid_examples/src/normalized.rs: -------------------------------------------------------------------------------- 1 | //! An example of constructing a strongly-typed wrapper around 2 | //! a normalized string value. 3 | //! 4 | //! The types in this module perform validation and normalization prior 5 | //! to allowing the type to be instantiated. If the value is already 6 | //! normalized, then the value is used without modification. Otherwise, 7 | //! an attempt is made to normalize the value. If the value cannot be 8 | //! normalized, then an error will be returned rather than allowing the 9 | //! invalid value to be constructed. 10 | //! 11 | //! Refer to the [`Normalizer`][aliri_braid::Normalizer] implementation 12 | //! for a given type for additional information on what is considered 13 | //! a valid or normalizable value for the type. 14 | 15 | use std::{borrow::Cow, convert::Infallible, error, fmt}; 16 | 17 | use aliri_braid::braid; 18 | 19 | /// An error indicating that the provided value was invalid 20 | #[derive(Debug)] 21 | pub enum InvalidString { 22 | EmptyString, 23 | InvalidCharacter, 24 | } 25 | 26 | impl fmt::Display for InvalidString { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | match self { 29 | Self::EmptyString => f.write_str("string cannot be empty"), 30 | Self::InvalidCharacter => f.write_str("string contains invalid uppercase character"), 31 | } 32 | } 33 | } 34 | 35 | impl From for InvalidString { 36 | #[inline(always)] 37 | fn from(x: Infallible) -> Self { 38 | match x {} 39 | } 40 | } 41 | 42 | impl error::Error for InvalidString {} 43 | 44 | /// A non-empty [`String`] normalized to lowercase 45 | /// 46 | /// This type maintains an invariant that ensures that a 47 | /// value of this type cannot be constructed that contains 48 | /// invalid data. Data that _can_ be normalized to a valid 49 | /// instance of this type will be. 50 | /// 51 | /// Because this type does normalization, the type explicitly 52 | /// does _not_ implement [`Borrow`][::std::borrow::Borrow], 53 | /// as doing so would could violate the contract of that trait, 54 | /// potentially resulting in lost data. If a user of 55 | /// the crate would like to override this, then they can 56 | /// explicitly implement the trait. 57 | /// 58 | /// This type includes an explicit parameter indicating that 59 | /// the borrowed form of this type should be named [`LowerStr`]. 60 | #[braid( 61 | serde, 62 | normalizer, 63 | ref_name = "LowerStr", 64 | ref_doc = "A borrowed reference to a non-empty, lowercase string" 65 | )] 66 | pub struct LowerString; 67 | 68 | impl aliri_braid::Validator for LowerString { 69 | type Error = InvalidString; 70 | 71 | fn validate(raw: &str) -> Result<(), Self::Error> { 72 | if raw.is_empty() { 73 | Err(InvalidString::EmptyString) 74 | } else if raw.chars().any(char::is_uppercase) { 75 | Err(InvalidString::InvalidCharacter) 76 | } else { 77 | Ok(()) 78 | } 79 | } 80 | } 81 | 82 | impl aliri_braid::Normalizer for LowerString { 83 | fn normalize(s: &str) -> Result, Self::Error> { 84 | if s.is_empty() { 85 | Err(InvalidString::EmptyString) 86 | } else if s.contains(char::is_uppercase) { 87 | Ok(Cow::Owned(s.to_lowercase())) 88 | } else { 89 | Ok(Cow::Borrowed(s)) 90 | } 91 | } 92 | } 93 | 94 | /// A non-empty [`String`] normalized to lowercase 95 | /// 96 | /// This type maintains an invariant that ensures that a 97 | /// value of this type cannot be constructed that contains 98 | /// invalid data. Data that _can_ be normalized to a valid 99 | /// instance of this type will be. 100 | /// 101 | /// Because this type does normalization, the type explicitly 102 | /// does _not_ implement [`Borrow`][::std::borrow::Borrow], 103 | /// as doing so would could violate the contract of that trait, 104 | /// potentially resulting in lost data. If a user of 105 | /// the crate would like to override this, then they can 106 | /// explicitly implement the trait. 107 | /// 108 | /// This type includes an explicit parameter indicating that 109 | /// the borrowed form of this type should be named [`LowerStr`]. 110 | #[braid( 111 | serde, 112 | normalizer, 113 | ref_doc = "A borrowed reference to a non-empty, lowercase string" 114 | )] 115 | pub struct LowerCompactString(compact_str::CompactString); 116 | 117 | impl aliri_braid::Validator for LowerCompactString { 118 | type Error = InvalidString; 119 | 120 | fn validate(raw: &str) -> Result<(), Self::Error> { 121 | if raw.is_empty() { 122 | Err(InvalidString::EmptyString) 123 | } else if raw.chars().any(char::is_uppercase) { 124 | Err(InvalidString::InvalidCharacter) 125 | } else { 126 | Ok(()) 127 | } 128 | } 129 | } 130 | 131 | impl aliri_braid::Normalizer for LowerCompactString { 132 | fn normalize(s: &str) -> Result, Self::Error> { 133 | if s.is_empty() { 134 | Err(InvalidString::EmptyString) 135 | } else if s.contains(char::is_uppercase) { 136 | Ok(Cow::Owned(s.to_lowercase())) 137 | } else { 138 | Ok(Cow::Borrowed(s)) 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /aliri_braid/tests/normalized.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, convert::Infallible, error, fmt}; 2 | 3 | use aliri_braid::braid; 4 | 5 | #[derive(Debug)] 6 | pub enum InvalidString { 7 | EmptyString, 8 | InvalidCharacter, 9 | } 10 | 11 | impl fmt::Display for InvalidString { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | match self { 14 | Self::EmptyString => f.write_str("string cannot be empty"), 15 | Self::InvalidCharacter => f.write_str("string contains invalid uppercase character"), 16 | } 17 | } 18 | } 19 | 20 | impl From for InvalidString { 21 | #[inline(always)] 22 | fn from(x: Infallible) -> Self { 23 | match x {} 24 | } 25 | } 26 | 27 | impl error::Error for InvalidString {} 28 | 29 | /// A non-empty [`String`] normalized to lowercase 30 | #[braid( 31 | serde, 32 | normalizer, 33 | ref_name = "LowerStr", 34 | ref_doc = "A borrowed reference to a non-empty, lowercase string" 35 | )] 36 | pub struct LowerString; 37 | 38 | impl aliri_braid::Validator for LowerString { 39 | type Error = InvalidString; 40 | 41 | fn validate(raw: &str) -> Result<(), Self::Error> { 42 | if raw.is_empty() { 43 | Err(InvalidString::EmptyString) 44 | } else if raw.chars().any(|c| c.is_uppercase()) { 45 | Err(InvalidString::InvalidCharacter) 46 | } else { 47 | Ok(()) 48 | } 49 | } 50 | } 51 | 52 | impl aliri_braid::Normalizer for LowerString { 53 | fn normalize(s: &str) -> Result, Self::Error> { 54 | if s.is_empty() { 55 | Err(InvalidString::EmptyString) 56 | } else if s.contains(|c: char| c.is_uppercase()) { 57 | Ok(Cow::Owned(s.to_lowercase())) 58 | } else { 59 | Ok(Cow::Borrowed(s)) 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | 68 | #[test] 69 | fn owned_handles_already_normal() { 70 | let x = LowerString::from_static("testing"); 71 | assert_eq!(x.as_str(), "testing"); 72 | } 73 | 74 | #[test] 75 | fn owned_handles_valid_non_normal() { 76 | let x = LowerString::from_static("TestIng"); 77 | assert_eq!(x.as_str(), "testing"); 78 | } 79 | 80 | #[test] 81 | fn owned_rejects_invalid() { 82 | let x = LowerString::new("".to_owned()); 83 | assert!(matches!(x, Err(_))); 84 | } 85 | 86 | #[test] 87 | fn ref_handles_already_normal() { 88 | let x = LowerStr::from_str("testing").unwrap(); 89 | assert!(matches!(x, Cow::Borrowed(_))); 90 | assert_eq!(x.as_str(), "testing"); 91 | } 92 | 93 | #[test] 94 | fn from_static_ref_handles_already_normal() { 95 | let x = LowerStr::from_static("testing"); 96 | assert_eq!(x.as_str(), "testing"); 97 | } 98 | 99 | #[test] 100 | fn ref_handles_valid_non_normal() { 101 | let x = LowerStr::from_str("TestIng").unwrap(); 102 | assert!(matches!(x, Cow::Owned(_))); 103 | assert_eq!(x.as_str(), "testing"); 104 | } 105 | 106 | #[test] 107 | #[should_panic] 108 | fn static_ref_handles_panics_on_non_normal() { 109 | LowerStr::from_static("TestIng"); 110 | } 111 | 112 | fn needs_ref(_: &LowerStr) {} 113 | fn needs_owned(_: LowerString) {} 114 | 115 | #[test] 116 | fn ref_as_ref_already_normal() { 117 | let cow = LowerStr::from_str("testing").unwrap(); 118 | let borrowed = cow.as_ref(); 119 | needs_ref(borrowed); 120 | } 121 | 122 | #[test] 123 | fn ref_as_ref_valid_non_normal() { 124 | let cow = LowerStr::from_str("TestIng").unwrap(); 125 | let borrowed = cow.as_ref(); 126 | needs_ref(borrowed); 127 | } 128 | 129 | #[test] 130 | fn ref_to_owned_already_normal() { 131 | let owned = LowerStr::from_str("testing").unwrap().into_owned(); 132 | needs_owned(owned); 133 | } 134 | 135 | #[test] 136 | fn ref_to_owned_valid_non_normal() { 137 | let owned = LowerStr::from_str("TestIng").unwrap().into_owned(); 138 | needs_owned(owned); 139 | } 140 | 141 | #[test] 142 | fn ref_rejects_invalid() { 143 | let x = LowerStr::from_str(""); 144 | assert!(matches!(x, Err(_))); 145 | } 146 | 147 | #[test] 148 | fn ref_norm_handles_already_normal() { 149 | let x = LowerStr::from_normalized_str("testing").unwrap(); 150 | assert_eq!(x.as_str(), "testing"); 151 | } 152 | 153 | #[test] 154 | fn ref_norm_rejects_valid_non_normal() { 155 | let x = LowerStr::from_normalized_str("TestIng"); 156 | assert!(matches!(x, Err(_))); 157 | } 158 | 159 | #[test] 160 | fn ref_norm_rejects_invalid() { 161 | let x = LowerStr::from_normalized_str(""); 162 | assert!(matches!(x, Err(_))); 163 | } 164 | 165 | #[allow(dead_code)] 166 | struct Bar<'a> { 167 | foo: std::borrow::Cow<'a, LowerStr>, 168 | } 169 | 170 | #[test] 171 | fn owned_as_cow() { 172 | let owned = LowerString::new("ORANGE".to_owned()).unwrap(); 173 | let _bar = Bar { foo: owned.into() }; 174 | } 175 | 176 | #[test] 177 | fn borrowed_as_cow() { 178 | let borrowed = LowerStr::from_normalized_str("orange").unwrap(); 179 | let _bar = Bar { 180 | foo: borrowed.into(), 181 | }; 182 | } 183 | 184 | #[test] 185 | fn owned_as_ref_borrowed() { 186 | let owned = LowerString::new("ORANGE".to_owned()).unwrap(); 187 | let _reference: &LowerStr = owned.as_ref(); 188 | } 189 | 190 | #[test] 191 | fn owned_as_ref_str() { 192 | let owned = LowerString::new("ORANGE".to_owned()).unwrap(); 193 | let _reference: &str = owned.as_ref(); 194 | } 195 | 196 | #[test] 197 | fn borrowed_as_ref_str() { 198 | let owned = LowerStr::from_normalized_str("orange").unwrap(); 199 | let _reference: &str = owned.as_ref(); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /aliri_braid/tests/too_many_braids.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, convert::Infallible, error, fmt}; 2 | #[derive(Debug)] 3 | pub enum InvalidString { 4 | EmptyString, 5 | InvalidCharacter, 6 | } 7 | 8 | impl fmt::Display for InvalidString { 9 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 10 | match self { 11 | Self::EmptyString => f.write_str("string cannot be empty"), 12 | Self::InvalidCharacter => f.write_str("string contains invalid uppercase character"), 13 | } 14 | } 15 | } 16 | 17 | impl From for InvalidString { 18 | #[inline(always)] 19 | fn from(x: Infallible) -> Self { 20 | match x {} 21 | } 22 | } 23 | 24 | impl error::Error for InvalidString {} 25 | 26 | pub struct LowerString; 27 | 28 | impl aliri_braid::Validator for LowerString { 29 | type Error = InvalidString; 30 | 31 | fn validate(raw: &str) -> Result<(), Self::Error> { 32 | if raw.is_empty() { 33 | Err(InvalidString::EmptyString) 34 | } else if raw.chars().any(|c| c.is_uppercase()) { 35 | Err(InvalidString::InvalidCharacter) 36 | } else { 37 | Ok(()) 38 | } 39 | } 40 | } 41 | 42 | impl aliri_braid::Normalizer for LowerString { 43 | fn normalize(s: &str) -> Result, Self::Error> { 44 | if s.is_empty() { 45 | Err(InvalidString::EmptyString) 46 | } else if s.contains(|c: char| c.is_uppercase()) { 47 | Ok(Cow::Owned(s.to_lowercase())) 48 | } else { 49 | Ok(Cow::Borrowed(s)) 50 | } 51 | } 52 | } 53 | 54 | #[derive(Debug)] 55 | pub enum InvalidScopeToken { 56 | EmptyString, 57 | InvalidCharacter { position: usize, value: u8 }, 58 | } 59 | 60 | impl fmt::Display for InvalidScopeToken { 61 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 62 | match self { 63 | Self::EmptyString => f.write_str("scope cannot be empty"), 64 | Self::InvalidCharacter { position, value } => f.write_fmt(format_args!( 65 | "invalid scope character at position {}: {:02x}", 66 | position, value 67 | )), 68 | } 69 | } 70 | } 71 | 72 | impl From for InvalidScopeToken { 73 | #[inline(always)] 74 | fn from(x: Infallible) -> Self { 75 | match x {} 76 | } 77 | } 78 | 79 | impl error::Error for InvalidScopeToken {} 80 | 81 | pub struct ScopeToken; 82 | 83 | impl aliri_braid::Validator for ScopeToken { 84 | type Error = InvalidScopeToken; 85 | 86 | fn validate(s: &str) -> Result<(), Self::Error> { 87 | if s.is_empty() { 88 | Err(InvalidScopeToken::EmptyString) 89 | } else if let Some((position, &value)) = s 90 | .as_bytes() 91 | .iter() 92 | .enumerate() 93 | .find(|(_, &b)| b <= 0x20 || b == 0x22 || b == 0x5C) 94 | { 95 | Err(InvalidScopeToken::InvalidCharacter { position, value }) 96 | } else { 97 | Ok(()) 98 | } 99 | } 100 | } 101 | 102 | macro_rules! too_many_braids { 103 | ($modname:ident) => { 104 | mod $modname { 105 | use aliri_braid::braid; 106 | 107 | /// A basic example of a wrapper around a [`String`] 108 | #[braid( 109 | serde, 110 | ref_doc = "A borrowed reference to a basic string slice wrapper" 111 | )] 112 | pub struct BasicExampleBuf; 113 | 114 | /// A non-empty [`String`] normalized to lowercase 115 | #[braid( 116 | serde, 117 | normalizer = "super::LowerString", 118 | ref_name = "LowerStr", 119 | ref_doc = "A borrowed reference to a non-empty, lowercase string" 120 | )] 121 | pub struct LowerString; 122 | 123 | /// A scope token as defined in RFC6749, Section 3.3 124 | #[braid( 125 | serde, 126 | validator = "super::ScopeToken", 127 | ref_doc = "A borrowed reference to a [`ScopeToken`]" 128 | )] 129 | pub struct ScopeToken; 130 | } 131 | }; 132 | } 133 | 134 | too_many_braids!(iter0); 135 | too_many_braids!(iter1); 136 | too_many_braids!(iter2); 137 | too_many_braids!(iter3); 138 | too_many_braids!(iter4); 139 | too_many_braids!(iter5); 140 | too_many_braids!(iter6); 141 | too_many_braids!(iter7); 142 | too_many_braids!(iter8); 143 | too_many_braids!(iter9); 144 | // too_many_braids!(iter10); 145 | // too_many_braids!(iter11); 146 | // too_many_braids!(iter12); 147 | // too_many_braids!(iter13); 148 | // too_many_braids!(iter14); 149 | // too_many_braids!(iter15); 150 | // too_many_braids!(iter16); 151 | // too_many_braids!(iter17); 152 | // too_many_braids!(iter18); 153 | // too_many_braids!(iter19); 154 | // too_many_braids!(iter20); 155 | // too_many_braids!(iter21); 156 | // too_many_braids!(iter22); 157 | // too_many_braids!(iter23); 158 | // too_many_braids!(iter24); 159 | // too_many_braids!(iter25); 160 | // too_many_braids!(iter26); 161 | // too_many_braids!(iter27); 162 | // too_many_braids!(iter28); 163 | // too_many_braids!(iter29); 164 | // too_many_braids!(iter30); 165 | // too_many_braids!(iter31); 166 | // too_many_braids!(iter32); 167 | // too_many_braids!(iter33); 168 | // too_many_braids!(iter34); 169 | // too_many_braids!(iter35); 170 | // too_many_braids!(iter36); 171 | // too_many_braids!(iter37); 172 | // too_many_braids!(iter38); 173 | // too_many_braids!(iter39); 174 | // too_many_braids!(iter40); 175 | // too_many_braids!(iter41); 176 | // too_many_braids!(iter42); 177 | // too_many_braids!(iter43); 178 | // too_many_braids!(iter44); 179 | // too_many_braids!(iter45); 180 | // too_many_braids!(iter46); 181 | // too_many_braids!(iter47); 182 | // too_many_braids!(iter48); 183 | // too_many_braids!(iter49); 184 | // too_many_braids!(iter50); 185 | // too_many_braids!(iter51); 186 | // too_many_braids!(iter52); 187 | // too_many_braids!(iter53); 188 | // too_many_braids!(iter54); 189 | // too_many_braids!(iter55); 190 | // too_many_braids!(iter56); 191 | // too_many_braids!(iter57); 192 | // too_many_braids!(iter58); 193 | // too_many_braids!(iter59); 194 | // too_many_braids!(iter60); 195 | // too_many_braids!(iter61); 196 | // too_many_braids!(iter62); 197 | // too_many_braids!(iter63); 198 | // too_many_braids!(iter64); 199 | // too_many_braids!(iter65); 200 | // too_many_braids!(iter66); 201 | // too_many_braids!(iter67); 202 | // too_many_braids!(iter68); 203 | // too_many_braids!(iter69); 204 | // too_many_braids!(iter70); 205 | // too_many_braids!(iter71); 206 | // too_many_braids!(iter72); 207 | // too_many_braids!(iter73); 208 | // too_many_braids!(iter74); 209 | // too_many_braids!(iter75); 210 | // too_many_braids!(iter76); 211 | // too_many_braids!(iter77); 212 | // too_many_braids!(iter78); 213 | // too_many_braids!(iter79); 214 | // too_many_braids!(iter80); 215 | // too_many_braids!(iter81); 216 | // too_many_braids!(iter82); 217 | // too_many_braids!(iter83); 218 | // too_many_braids!(iter84); 219 | // too_many_braids!(iter85); 220 | // too_many_braids!(iter86); 221 | // too_many_braids!(iter87); 222 | // too_many_braids!(iter88); 223 | // too_many_braids!(iter89); 224 | // too_many_braids!(iter90); 225 | // too_many_braids!(iter91); 226 | // too_many_braids!(iter92); 227 | // too_many_braids!(iter93); 228 | // too_many_braids!(iter94); 229 | // too_many_braids!(iter95); 230 | // too_many_braids!(iter96); 231 | // too_many_braids!(iter97); 232 | // too_many_braids!(iter98); 233 | // too_many_braids!(iter99); 234 | -------------------------------------------------------------------------------- /aliri_braid/tests/validated.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, error, fmt}; 2 | 3 | use aliri_braid::braid; 4 | 5 | #[derive(Debug)] 6 | pub enum InvalidScopeToken { 7 | EmptyString, 8 | InvalidCharacter { position: usize, value: u8 }, 9 | } 10 | 11 | impl fmt::Display for InvalidScopeToken { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | match self { 14 | Self::EmptyString => f.write_str("scope cannot be empty"), 15 | Self::InvalidCharacter { position, value } => f.write_fmt(format_args!( 16 | "invalid scope character at position {}: {:02x}", 17 | position, value 18 | )), 19 | } 20 | } 21 | } 22 | 23 | impl From for InvalidScopeToken { 24 | #[inline(always)] 25 | fn from(x: Infallible) -> Self { 26 | match x {} 27 | } 28 | } 29 | 30 | impl error::Error for InvalidScopeToken {} 31 | 32 | /// A scope token as defined in RFC6749, Section 3.3 33 | #[braid(serde, validator, ref_doc = "A borrowed reference to a [`ScopeToken`]")] 34 | pub struct ScopeToken; 35 | 36 | impl aliri_braid::Validator for ScopeToken { 37 | type Error = InvalidScopeToken; 38 | 39 | fn validate(s: &str) -> Result<(), Self::Error> { 40 | if s.is_empty() { 41 | Err(InvalidScopeToken::EmptyString) 42 | } else if let Some((position, &value)) = s 43 | .as_bytes() 44 | .iter() 45 | .enumerate() 46 | .find(|(_, &b)| b <= 0x20 || b == 0x22 || b == 0x5C || 0x7F <= b) 47 | { 48 | Err(InvalidScopeToken::InvalidCharacter { position, value }) 49 | } else { 50 | Ok(()) 51 | } 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use std::borrow::Borrow; 58 | 59 | use super::*; 60 | 61 | #[test] 62 | fn owned_handles_valid() { 63 | let x = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 64 | assert_eq!(x.as_str(), "https://crates.io/scopes/publish:crate"); 65 | } 66 | 67 | #[test] 68 | fn owned_rejects_empty() { 69 | let x = ScopeToken::new("".to_owned()); 70 | assert!(matches!(x, Err(InvalidScopeToken::EmptyString))); 71 | } 72 | 73 | #[test] 74 | fn owned_rejects_invalid_quote() { 75 | let x = ScopeToken::new("https://crates.io/scopes/\"publish:crate\"".to_owned()); 76 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 77 | } 78 | 79 | #[test] 80 | fn owned_rejects_invalid_control() { 81 | let x = ScopeToken::new("https://crates.io/scopes/\tpublish:crate".to_owned()); 82 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 83 | } 84 | 85 | #[test] 86 | fn owned_rejects_invalid_backslash() { 87 | let x = ScopeToken::new("https://crates.io/scopes/\\publish:crate".to_owned()); 88 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 89 | } 90 | 91 | #[test] 92 | fn owned_rejects_invalid_delete() { 93 | let x = ScopeToken::new("https://crates.io/scopes/\x7Fpublish:crate".to_owned()); 94 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 95 | } 96 | 97 | #[test] 98 | fn owned_rejects_invalid_non_ascii() { 99 | let x = ScopeToken::new("https://crates.io/scopes/¿publish:crate".to_owned()); 100 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 101 | } 102 | 103 | #[test] 104 | fn owned_rejects_invalid_emoji() { 105 | let x = ScopeToken::new("https://crates.io/scopes/🪤publish:crate".to_owned()); 106 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 107 | } 108 | 109 | #[test] 110 | fn ref_handles_valid() { 111 | let x = ScopeTokenRef::from_static("https://crates.io/scopes/publish:crate"); 112 | assert_eq!(x.as_str(), "https://crates.io/scopes/publish:crate"); 113 | } 114 | 115 | #[test] 116 | fn ref_rejects_empty() { 117 | let x = ScopeTokenRef::from_str(""); 118 | assert!(matches!(x, Err(InvalidScopeToken::EmptyString))); 119 | } 120 | 121 | #[test] 122 | #[should_panic] 123 | fn from_static_ref_panics_on_empty() { 124 | ScopeTokenRef::from_static(""); 125 | } 126 | 127 | #[test] 128 | #[should_panic] 129 | fn from_static_owned_panics_on_empty() { 130 | ScopeToken::from_static(""); 131 | } 132 | 133 | #[test] 134 | fn ref_rejects_invalid_quote() { 135 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/\"publish:crate\""); 136 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 137 | } 138 | 139 | #[test] 140 | fn ref_rejects_invalid_control() { 141 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/\tpublish:crate"); 142 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 143 | } 144 | 145 | #[test] 146 | fn ref_rejects_invalid_backslash() { 147 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/\\publish:crate"); 148 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 149 | } 150 | 151 | #[test] 152 | fn ref_rejects_invalid_delete() { 153 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/\x7Fpublish:crate"); 154 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 155 | } 156 | 157 | #[test] 158 | fn ref_rejects_invalid_non_ascii() { 159 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/¿publish:crate"); 160 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 161 | } 162 | 163 | #[test] 164 | fn ref_rejects_invalid_emoji() { 165 | let x = ScopeTokenRef::from_str("https://crates.io/scopes/🪤publish:crate"); 166 | assert!(matches!(x, Err(InvalidScopeToken::InvalidCharacter { .. }))); 167 | } 168 | 169 | #[allow(dead_code)] 170 | struct Bar<'a> { 171 | foo: std::borrow::Cow<'a, ScopeTokenRef>, 172 | } 173 | 174 | #[test] 175 | fn owned_as_cow() { 176 | let owned = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 177 | let _bar = Bar { foo: owned.into() }; 178 | } 179 | 180 | #[test] 181 | fn borrowed_as_cow() { 182 | let borrowed = ScopeTokenRef::from_static("https://crates.io/scopes/publish:crate"); 183 | let _bar = Bar { 184 | foo: borrowed.into(), 185 | }; 186 | } 187 | 188 | #[test] 189 | fn owned_as_ref_borrowed() { 190 | let owned = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 191 | let _reference: &ScopeTokenRef = owned.as_ref(); 192 | } 193 | 194 | #[test] 195 | fn owned_as_ref_str() { 196 | let owned = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 197 | let _reference: &str = owned.as_ref(); 198 | } 199 | 200 | #[test] 201 | fn borrowed_as_ref_str() { 202 | let owned = ScopeTokenRef::from_static("https://crates.io/scopes/publish:crate"); 203 | let _reference: &str = owned.as_ref(); 204 | } 205 | 206 | #[test] 207 | fn owned_borrow_borrowed() { 208 | let owned = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 209 | let _reference: &ScopeToken = owned.borrow(); 210 | } 211 | 212 | #[test] 213 | fn owned_borrow_str() { 214 | let owned = ScopeToken::from_static("https://crates.io/scopes/publish:crate"); 215 | let _reference: &str = owned.borrow(); 216 | } 217 | 218 | #[test] 219 | fn borrowed_borrow_str() { 220 | let owned = ScopeTokenRef::from_static("https://crates.io/scopes/publish:crate"); 221 | let _reference: &str = owned.borrow(); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /aliri_braid/tests/jumble.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | 3 | use std::{borrow::Cow, convert::Infallible, fmt}; 4 | 5 | use aliri_braid::braid; 6 | 7 | mod common; 8 | 9 | #[braid] 10 | pub struct Basic; 11 | 12 | #[braid(ref_name = "SomeRefName")] 13 | pub struct CustomRefName; 14 | 15 | #[braid(validator = "ValidatedBuf")] 16 | pub struct ExternallyValidated; 17 | 18 | #[braid(ref_name = "SomeValidatedRefName", validator = "ValidatedBuf")] 19 | pub struct ValidatedWithCustomRefName; 20 | 21 | #[braid(serde)] 22 | pub struct Orange; 23 | 24 | #[braid] 25 | pub struct OrangeWithNamedField { 26 | id: String, 27 | } 28 | 29 | #[test] 30 | fn internal_access_to_named_ref_field_compile_test() { 31 | let x = OrangeWithNamedFieldRef::from_static("thing"); 32 | let _ = &x.id; 33 | } 34 | 35 | #[braid( 36 | serde, 37 | validator, 38 | ref_doc = "A reference to a cool new orange, that isn't yours!" 39 | )] 40 | pub struct ValidatedBuf; 41 | 42 | #[derive(Debug, PartialEq, Eq)] 43 | pub struct InvalidData; 44 | 45 | impl std::fmt::Display for InvalidData { 46 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 47 | f.write_str("found 4-byte UTF-8 codepoints") 48 | } 49 | } 50 | 51 | impl From for InvalidData { 52 | #[inline(always)] 53 | fn from(x: Infallible) -> Self { 54 | match x {} 55 | } 56 | } 57 | 58 | impl std::error::Error for InvalidData {} 59 | 60 | impl aliri_braid::Validator for ValidatedBuf { 61 | type Error = InvalidData; 62 | fn validate(s: &str) -> Result<(), Self::Error> { 63 | if s.chars().any(|c| c.len_utf8() > 3) { 64 | Err(InvalidData) 65 | } else { 66 | Ok(()) 67 | } 68 | } 69 | } 70 | 71 | #[braid( 72 | serde, 73 | normalizer, 74 | ref_doc = "A reference to a cool new orange, that isn't yours!" 75 | )] 76 | pub struct NormalizedBuf; 77 | 78 | impl aliri_braid::Validator for NormalizedBuf { 79 | type Error = InvalidData; 80 | 81 | fn validate(raw: &str) -> Result<(), Self::Error> { 82 | if raw.chars().any(|c| c.len_utf8() > 3 || c == ' ') { 83 | Err(InvalidData) 84 | } else { 85 | Ok(()) 86 | } 87 | } 88 | } 89 | 90 | impl aliri_braid::Normalizer for NormalizedBuf { 91 | fn normalize(s: &str) -> Result, Self::Error> { 92 | if s.chars().any(|c| c.len_utf8() > 3) { 93 | Err(InvalidData) 94 | } else if s.contains(' ') { 95 | Ok(Cow::Owned(s.replace(' ', ""))) 96 | } else { 97 | Ok(Cow::Borrowed(s)) 98 | } 99 | } 100 | } 101 | 102 | #[braid(clone = "omit", debug = "omit", display = "omit")] 103 | pub struct CustomImpls; 104 | 105 | impl fmt::Debug for CustomImpls { 106 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 107 | f.write_str("Owned Debug") 108 | } 109 | } 110 | 111 | impl fmt::Display for CustomImpls { 112 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 113 | f.write_str("Owned Display") 114 | } 115 | } 116 | 117 | impl fmt::Debug for CustomImplsRef { 118 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 119 | f.write_str("Borrowed Debug") 120 | } 121 | } 122 | 123 | impl fmt::Display for CustomImplsRef { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | f.write_str("Borrowed Display") 126 | } 127 | } 128 | 129 | #[braid(debug = "owned", display = "owned")] 130 | pub struct DelegatedImpls; 131 | 132 | impl fmt::Debug for DelegatedImplsRef { 133 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 134 | f.write_str("Borrowed Debug") 135 | } 136 | } 137 | 138 | impl fmt::Display for DelegatedImplsRef { 139 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 140 | f.write_str("Borrowed Display") 141 | } 142 | } 143 | 144 | #[braid(debug = "owned", display = "owned")] 145 | pub struct Secret; 146 | 147 | impl fmt::Debug for SecretRef { 148 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 149 | if f.alternate() { 150 | f.write_str("\"")?; 151 | let max_len = f.width().unwrap_or(10); 152 | if max_len <= 1 { 153 | f.write_str("…")?; 154 | } else { 155 | match self.0.char_indices().nth(max_len - 2) { 156 | Some((idx, c)) if idx + c.len_utf8() < self.0.len() => { 157 | f.write_str(&self.0[0..idx + c.len_utf8()])?; 158 | f.write_str("…")?; 159 | } 160 | _ => { 161 | f.write_str(&self.0)?; 162 | } 163 | } 164 | } 165 | f.write_str("\"") 166 | } else { 167 | f.write_str("***SECRET***") 168 | } 169 | } 170 | } 171 | 172 | impl fmt::Display for SecretRef { 173 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 174 | if f.alternate() { 175 | f.write_str(&self.0) 176 | } else { 177 | f.write_str("***SECRET***") 178 | } 179 | } 180 | } 181 | 182 | mod tests { 183 | use super::*; 184 | 185 | #[test] 186 | fn check_custom_no_impl_clone() { 187 | static_assertions::assert_not_impl_any!(CustomImpls: Clone); 188 | } 189 | 190 | #[test] 191 | fn check_custom_debug() { 192 | let v = CustomImpls::from_static(""); 193 | let vref: &CustomImplsRef = &v; 194 | assert_eq!("Owned Debug", format!("{:?}", v)); 195 | assert_eq!("Borrowed Debug", format!("{:?}", vref)); 196 | } 197 | 198 | #[test] 199 | fn check_custom_display() { 200 | let v = CustomImpls::from_static(""); 201 | let vref: &CustomImplsRef = &v; 202 | assert_eq!("Owned Display", format!("{}", v)); 203 | assert_eq!("Borrowed Display", format!("{}", vref)); 204 | } 205 | 206 | #[test] 207 | fn check_delegated_impl_clone() { 208 | static_assertions::assert_impl_all!(DelegatedImpls: Clone); 209 | } 210 | 211 | #[test] 212 | fn check_delegated_debug() { 213 | let v = DelegatedImpls::from_static(""); 214 | let vref: &DelegatedImplsRef = &v; 215 | assert_eq!("Borrowed Debug", format!("{:?}", v)); 216 | assert_eq!("Borrowed Debug", format!("{:?}", vref)); 217 | } 218 | 219 | #[test] 220 | fn check_delegated_display() { 221 | let v = DelegatedImpls::from_static(""); 222 | let vref: &DelegatedImplsRef = &v; 223 | assert_eq!("Borrowed Display", format!("{}", v)); 224 | assert_eq!("Borrowed Display", format!("{}", vref)); 225 | } 226 | 227 | #[test] 228 | fn check_secret_impl_clone() { 229 | static_assertions::assert_impl_all!(Secret: Clone); 230 | } 231 | 232 | #[test] 233 | fn check_secret_debug() { 234 | let v = Secret::from_static("my secret is bananas"); 235 | let vref: &SecretRef = &v; 236 | assert_eq!("***SECRET***", format!("{:?}", v)); 237 | assert_eq!("\"my secret…\"", format!("{:#?}", v)); 238 | assert_eq!("\"…\"", format!("{:#1?}", v)); 239 | assert_eq!("\"my secret is…\"", format!("{:#13?}", v)); 240 | assert_eq!("\"my secret is banana…\"", format!("{:#20?}", v)); 241 | assert_eq!("\"my secret is bananas\"", format!("{:#21?}", v)); 242 | 243 | assert_eq!("***SECRET***", format!("{:?}", vref)); 244 | assert_eq!("\"my secret…\"", format!("{:#?}", vref)); 245 | assert_eq!("\"…\"", format!("{:#1?}", vref)); 246 | assert_eq!("\"my secret is…\"", format!("{:#13?}", vref)); 247 | assert_eq!("\"my secret is banana…\"", format!("{:#20?}", vref)); 248 | assert_eq!("\"my secret is bananas\"", format!("{:#21?}", vref)); 249 | } 250 | 251 | #[test] 252 | fn check_secret_display() { 253 | let v = Secret::from_static("my secret is bananas"); 254 | let vref: &SecretRef = &v; 255 | assert_eq!("***SECRET***", format!("{}", v)); 256 | assert_eq!("my secret is bananas", format!("{:#}", v)); 257 | assert_eq!("***SECRET***", format!("{}", vref)); 258 | assert_eq!("my secret is bananas", format!("{:#}", vref)); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /aliri_braid/tests/common/infallible.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashSet}, 3 | convert::TryInto, 4 | }; 5 | 6 | use quickcheck_macros::quickcheck; 7 | use static_assertions::{assert_eq_align, assert_eq_size, assert_eq_size_ptr, assert_eq_size_val}; 8 | 9 | use crate::{Orange, OrangeRef}; 10 | 11 | #[test] 12 | pub fn equality_tests() { 13 | let x = Orange::from_static("One"); 14 | let y = OrangeRef::from_static("One"); 15 | 16 | assert_eq!(x, y); 17 | assert_eq!(x, *y); 18 | assert_eq!(&x, y); 19 | assert_eq!(y, x); 20 | assert_eq!(y, &x); 21 | assert_eq!(*y, x); 22 | 23 | assert_eq!("One", x.clone().take()); 24 | let z = x.clone().into_boxed_ref(); 25 | assert_eq!(y, &*z); 26 | assert_eq!(&*z, y); 27 | assert_eq!(x, &*z); 28 | assert_eq!(&*z, x); 29 | 30 | assert_eq!(x, z.into_owned()); 31 | } 32 | 33 | #[test] 34 | pub fn debug_and_display_tests() { 35 | let x = Orange::from_static("One"); 36 | let y = OrangeRef::from_static("One"); 37 | 38 | assert_eq!("One", x.to_string()); 39 | assert_eq!("One", y.to_string()); 40 | assert_eq!("\"One\"", format!("{:?}", x)); 41 | assert_eq!("\"One\"", format!("{:?}", y)); 42 | } 43 | 44 | #[cfg_attr(miri, ignore = "takes too long on miri")] 45 | #[quickcheck] 46 | fn owned_and_borrowed_hashes_are_equivalent(s: String) -> bool { 47 | use std::{ 48 | collections::hash_map::DefaultHasher, 49 | hash::{Hash, Hasher}, 50 | }; 51 | 52 | let owned = Orange::new(s.clone()); 53 | 54 | let owned_hash = { 55 | let mut hasher = DefaultHasher::new(); 56 | owned.hash(&mut hasher); 57 | hasher.finish() 58 | }; 59 | 60 | let borrowed = OrangeRef::from_str(&s); 61 | 62 | let borrowed_hash = { 63 | let mut hasher = DefaultHasher::new(); 64 | borrowed.hash(&mut hasher); 65 | hasher.finish() 66 | }; 67 | 68 | owned_hash == borrowed_hash 69 | } 70 | 71 | #[test] 72 | pub fn parsing_owned_pass() -> Result<(), Box> { 73 | let x: Orange = "One".parse()?; 74 | assert_eq!("One", x.as_str()); 75 | Ok(()) 76 | } 77 | 78 | #[test] 79 | pub fn from_owned_pass() { 80 | let x: Orange = "One".into(); 81 | assert_eq!("One", x.as_str()); 82 | } 83 | 84 | #[test] 85 | pub fn try_from_owned_pass() -> Result<(), Box> { 86 | let x: Orange = "One".try_into()?; 87 | assert_eq!("One", x.as_str()); 88 | Ok(()) 89 | } 90 | 91 | #[test] 92 | pub fn try_from_borrowed_pass() -> Result<(), Box> { 93 | let x: &OrangeRef = "One".try_into()?; 94 | assert_eq!("One", x.as_str()); 95 | Ok(()) 96 | } 97 | 98 | #[test] 99 | fn can_use_as_hash_keys() { 100 | let mut map = HashSet::new(); 101 | 102 | assert!(map.insert(Orange::from_static("One"))); 103 | assert!(map.insert(Orange::from_static("Seven"))); 104 | 105 | assert!(map.contains(OrangeRef::from_static("One"))); 106 | assert!(map.contains(&Orange::from_static("One"))); 107 | assert!(!map.contains(OrangeRef::from_static("Two"))); 108 | 109 | assert!(!map.remove(OrangeRef::from_static("Two"))); 110 | assert!(map.remove(OrangeRef::from_static("One"))); 111 | assert!(!map.remove(OrangeRef::from_static("One"))); 112 | 113 | assert!(map.remove(&Orange::from_static("Seven"))); 114 | assert!(!map.remove(OrangeRef::from_static("Seven"))); 115 | 116 | assert!(map.is_empty()); 117 | } 118 | 119 | #[test] 120 | fn can_use_refs_as_hash_keys() { 121 | let mut map = HashSet::new(); 122 | 123 | assert!(map.insert(OrangeRef::from_str("One"))); 124 | assert!(map.insert(OrangeRef::from_str("Seven"))); 125 | 126 | assert!(map.contains(OrangeRef::from_str("One"))); 127 | assert!(map.contains(&*Orange::from_static("One"))); 128 | assert!(!map.contains(OrangeRef::from_str("Two"))); 129 | 130 | assert!(!map.remove(OrangeRef::from_str("Two"))); 131 | assert!(map.remove(OrangeRef::from_str("One"))); 132 | assert!(!map.remove(OrangeRef::from_str("One"))); 133 | 134 | assert!(map.remove(&*Orange::from_static("Seven"))); 135 | assert!(!map.remove(OrangeRef::from_str("Seven"))); 136 | 137 | assert!(map.is_empty()); 138 | } 139 | 140 | #[test] 141 | fn can_use_as_btree_keys() { 142 | let mut map = BTreeSet::new(); 143 | 144 | assert!(map.insert(Orange::from_static("One"))); 145 | assert!(map.insert(Orange::from_static("Seven"))); 146 | 147 | assert!(map.contains(OrangeRef::from_static("One"))); 148 | assert!(map.contains(&Orange::from_static("One"))); 149 | assert!(!map.contains(OrangeRef::from_static("Two"))); 150 | 151 | assert!(!map.remove(OrangeRef::from_static("Two"))); 152 | assert!(map.remove(OrangeRef::from_static("One"))); 153 | assert!(!map.remove(OrangeRef::from_static("One"))); 154 | 155 | assert!(map.remove(&Orange::from_static("Seven"))); 156 | assert!(!map.remove(OrangeRef::from_static("Seven"))); 157 | 158 | assert!(map.is_empty()); 159 | } 160 | 161 | #[test] 162 | fn can_use_refs_as_btree_keys() { 163 | let mut map = BTreeSet::new(); 164 | 165 | assert!(map.insert(OrangeRef::from_str("One"))); 166 | assert!(map.insert(OrangeRef::from_str("Seven"))); 167 | 168 | assert!(map.contains(OrangeRef::from_str("One"))); 169 | assert!(map.contains(&*Orange::from_static("One"))); 170 | assert!(!map.contains(OrangeRef::from_str("Two"))); 171 | 172 | assert!(!map.remove(OrangeRef::from_str("Two"))); 173 | assert!(map.remove(OrangeRef::from_str("One"))); 174 | assert!(!map.remove(OrangeRef::from_str("One"))); 175 | 176 | assert!(map.remove(&*Orange::from_static("Seven"))); 177 | assert!(!map.remove(OrangeRef::from_str("Seven"))); 178 | 179 | assert!(map.is_empty()); 180 | } 181 | 182 | #[test] 183 | fn verify_serialization_non_validated() -> Result<(), Box> { 184 | const SOURCE: &str = "Test 🏗"; 185 | const EXPECTED_SERIALIZATION: &str = "\"Test 🏗\""; 186 | 187 | let start = Orange::from_static(SOURCE); 188 | 189 | let own_serialized = serde_json::to_string(&start)?; 190 | assert_eq!(EXPECTED_SERIALIZATION, own_serialized); 191 | let borrow: &OrangeRef = serde_json::from_str(&own_serialized)?; 192 | assert_eq!(start, borrow); 193 | let borrow_serialized = serde_json::to_string(borrow)?; 194 | assert_eq!(EXPECTED_SERIALIZATION, borrow_serialized); 195 | let boxed: Box = serde_json::from_str(&borrow_serialized)?; 196 | assert_eq!(borrow, &*boxed); 197 | let box_serialized = serde_json::to_string(&boxed)?; 198 | assert_eq!(EXPECTED_SERIALIZATION, box_serialized); 199 | let owned: Orange = serde_json::from_str(&box_serialized)?; 200 | assert_eq!(*boxed, *owned); 201 | 202 | assert_eq!(owned, start); 203 | Ok(()) 204 | } 205 | 206 | #[test] 207 | fn check_reference_alignment() { 208 | dbg!(std::mem::align_of::<&str>()); 209 | dbg!(std::mem::align_of::<&OrangeRef>()); 210 | assert_eq_align!(&OrangeRef, &str); 211 | } 212 | 213 | #[test] 214 | fn check_reference_size() { 215 | dbg!(std::mem::size_of::<&str>()); 216 | dbg!(std::mem::size_of::<&OrangeRef>()); 217 | assert_eq_size!(&OrangeRef, &str); 218 | } 219 | 220 | #[test] 221 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 222 | fn check_reference_size_ptr() { 223 | let s = "source"; 224 | let y: &OrangeRef = OrangeRef::from_str(s); 225 | assert_eq_size_ptr!(&s, &y); 226 | } 227 | 228 | #[test] 229 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 230 | fn check_reference_size_val() { 231 | let s = "source"; 232 | let y: &OrangeRef = OrangeRef::from_str(s); 233 | dbg!(std::mem::size_of_val(s)); 234 | dbg!(std::mem::size_of_val(y)); 235 | assert_eq_size_val!(s, y); 236 | } 237 | 238 | #[test] 239 | fn check_boxed_ref_alignment() { 240 | dbg!(std::mem::align_of::>()); 241 | dbg!(std::mem::align_of::>()); 242 | assert_eq_align!(Box, Box); 243 | } 244 | 245 | #[test] 246 | fn check_boxed_ref_size() { 247 | dbg!(std::mem::size_of::>()); 248 | dbg!(std::mem::size_of::>()); 249 | assert_eq_size!(Box, Box); 250 | } 251 | 252 | #[test] 253 | fn check_boxed_ref_size_ptr() { 254 | let source = String::from("source"); 255 | let s = source.clone().into_boxed_str(); 256 | let y: Box = Orange::new(source).into_boxed_ref(); 257 | assert_eq_size_ptr!(&s, &y); 258 | } 259 | 260 | #[test] 261 | fn check_boxed_ref_size_val() { 262 | let source = String::from("source"); 263 | let s = source.clone().into_boxed_str(); 264 | let y: Box = Orange::new(source).into_boxed_ref(); 265 | dbg!(std::mem::size_of_val(&s)); 266 | dbg!(std::mem::size_of_val(&y)); 267 | assert_eq_size_val!(s, y); 268 | } 269 | 270 | #[test] 271 | fn check_owned_alignment() { 272 | dbg!(std::mem::align_of::()); 273 | dbg!(std::mem::align_of::()); 274 | assert_eq_align!(Orange, String); 275 | } 276 | 277 | #[test] 278 | fn check_owned_size() { 279 | dbg!(std::mem::size_of::()); 280 | dbg!(std::mem::size_of::()); 281 | assert_eq_size!(Orange, String); 282 | } 283 | 284 | #[test] 285 | fn check_owned_size_ptr() { 286 | let s = String::from("source"); 287 | let y: Orange = Orange::new(s.clone()); 288 | assert_eq_size_ptr!(&s, &y); 289 | } 290 | 291 | #[test] 292 | fn check_owned_size_val() { 293 | let s = String::from("source"); 294 | let y: Orange = Orange::new(s.clone()); 295 | dbg!(std::mem::size_of_val(&s)); 296 | dbg!(std::mem::size_of_val(&y)); 297 | assert_eq_size_val!(s, y); 298 | } 299 | 300 | assert_core_impls!(Orange => OrangeRef); 301 | -------------------------------------------------------------------------------- /aliri_braid/tests/common/fallible.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashSet}, 3 | convert::TryInto, 4 | }; 5 | 6 | use quickcheck_macros::quickcheck; 7 | use static_assertions::{assert_eq_align, assert_eq_size, assert_eq_size_ptr, assert_eq_size_val}; 8 | 9 | use crate::{Validated, ValidatedBuf}; 10 | 11 | #[test] 12 | pub fn equality_tests() -> Result<(), Box> { 13 | let x = ValidatedBuf::from_static("One"); 14 | let y = Validated::from_static("One"); 15 | assert_eq!(x, y); 16 | assert_eq!(x, *y); 17 | assert_eq!(&x, y); 18 | assert_eq!(y, x); 19 | assert_eq!(y, &x); 20 | assert_eq!(*y, x); 21 | 22 | assert_eq!("One", x.clone().take()); 23 | let z = x.clone().into_boxed_ref(); 24 | assert_eq!(y, &*z); 25 | assert_eq!(&*z, y); 26 | assert_eq!(x, &*z); 27 | assert_eq!(&*z, x); 28 | 29 | assert_eq!(x, z.into_owned()); 30 | 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | pub fn parsing_owned_pass() -> Result<(), Box> { 36 | let x: ValidatedBuf = "One".parse()?; 37 | assert_eq!("One", x.as_str()); 38 | Ok(()) 39 | } 40 | 41 | #[test] 42 | #[should_panic] 43 | pub fn parsing_owned_fails() { 44 | let _: ValidatedBuf = "Test 🏗".parse().unwrap(); 45 | } 46 | 47 | #[test] 48 | pub fn try_from_owned_pass() -> Result<(), Box> { 49 | let x: ValidatedBuf = "One".try_into()?; 50 | assert_eq!("One", x.as_str()); 51 | Ok(()) 52 | } 53 | 54 | #[test] 55 | #[should_panic] 56 | pub fn try_from_owned_fails() { 57 | let _: ValidatedBuf = "Test 🏗".try_into().unwrap(); 58 | } 59 | 60 | #[test] 61 | pub fn try_from_borrowed_pass() -> Result<(), Box> { 62 | let x: &Validated = "One".try_into()?; 63 | assert_eq!("One", x.as_str()); 64 | Ok(()) 65 | } 66 | 67 | #[test] 68 | #[should_panic] 69 | pub fn try_from_borrowed_fails() { 70 | let _: &Validated = "Test 🏗".try_into().unwrap(); 71 | } 72 | 73 | #[test] 74 | fn debug_and_display_tests() { 75 | let x = ValidatedBuf::from_static("One"); 76 | let y = Validated::from_static("One"); 77 | 78 | assert_eq!("One", x.to_string()); 79 | assert_eq!("One", y.to_string()); 80 | assert_eq!("\"One\"", format!("{:?}", x)); 81 | assert_eq!("\"One\"", format!("{:?}", y)); 82 | } 83 | 84 | #[cfg_attr(miri, ignore = "takes too long on miri")] 85 | #[quickcheck] 86 | fn owned_and_borrowed_hashes_are_equivalent(s: String) -> quickcheck::TestResult { 87 | use std::{ 88 | collections::hash_map::DefaultHasher, 89 | hash::{Hash, Hasher}, 90 | }; 91 | 92 | let owned = if let Ok(x) = ValidatedBuf::new(s.clone()) { 93 | x 94 | } else { 95 | return quickcheck::TestResult::discard(); 96 | }; 97 | 98 | let owned_hash = { 99 | let mut hasher = DefaultHasher::new(); 100 | owned.hash(&mut hasher); 101 | hasher.finish() 102 | }; 103 | 104 | let borrowed = Validated::from_str(&s).unwrap(); 105 | 106 | let borrowed_hash = { 107 | let mut hasher = DefaultHasher::new(); 108 | borrowed.hash(&mut hasher); 109 | hasher.finish() 110 | }; 111 | 112 | if owned_hash == borrowed_hash { 113 | quickcheck::TestResult::passed() 114 | } else { 115 | quickcheck::TestResult::failed() 116 | } 117 | } 118 | 119 | #[test] 120 | fn can_use_as_hash_keys() { 121 | let mut map = HashSet::new(); 122 | 123 | assert!(map.insert(ValidatedBuf::from_static("One"))); 124 | assert!(map.insert(ValidatedBuf::from_static("Seven"))); 125 | 126 | assert!(map.contains(Validated::from_static("One"))); 127 | assert!(map.contains(&ValidatedBuf::from_static("One"))); 128 | assert!(!map.contains(Validated::from_static("Two"))); 129 | 130 | assert!(!map.remove(Validated::from_static("Two"))); 131 | assert!(map.remove(Validated::from_static("One"))); 132 | assert!(!map.remove(Validated::from_static("One"))); 133 | 134 | assert!(map.remove(&ValidatedBuf::from_static("Seven"))); 135 | assert!(!map.remove(Validated::from_static("Seven"))); 136 | 137 | assert!(map.is_empty()); 138 | } 139 | 140 | #[test] 141 | fn can_use_refs_as_hash_keys() { 142 | let mut map = HashSet::new(); 143 | 144 | assert!(map.insert(Validated::from_static("One"))); 145 | assert!(map.insert(Validated::from_static("Seven"))); 146 | 147 | assert!(map.contains(Validated::from_static("One"))); 148 | assert!(map.contains(&*ValidatedBuf::from_static("One"))); 149 | assert!(!map.contains(Validated::from_static("Two"))); 150 | 151 | assert!(!map.remove(Validated::from_static("Two"))); 152 | assert!(map.remove(Validated::from_static("One"))); 153 | assert!(!map.remove(Validated::from_static("One"))); 154 | 155 | assert!(map.remove(&*ValidatedBuf::from_static("Seven"))); 156 | assert!(!map.remove(Validated::from_static("Seven"))); 157 | 158 | assert!(map.is_empty()); 159 | } 160 | 161 | #[test] 162 | fn can_use_as_btree_keys() { 163 | let mut map = BTreeSet::new(); 164 | 165 | assert!(map.insert(ValidatedBuf::from_static("One"))); 166 | assert!(map.insert(ValidatedBuf::from_static("Seven"))); 167 | 168 | assert!(map.contains(Validated::from_static("One"))); 169 | assert!(map.contains(&ValidatedBuf::from_static("One"))); 170 | assert!(!map.contains(Validated::from_static("Two"))); 171 | 172 | assert!(!map.remove(Validated::from_static("Two"))); 173 | assert!(map.remove(Validated::from_static("One"))); 174 | assert!(!map.remove(Validated::from_static("One"))); 175 | 176 | assert!(map.remove(&ValidatedBuf::from_static("Seven"))); 177 | assert!(!map.remove(Validated::from_static("Seven"))); 178 | 179 | assert!(map.is_empty()); 180 | } 181 | 182 | #[test] 183 | fn can_use_refs_as_btree_keys() { 184 | let mut map = BTreeSet::new(); 185 | 186 | assert!(map.insert(Validated::from_static("One"))); 187 | assert!(map.insert(Validated::from_static("Seven"))); 188 | 189 | assert!(map.contains(Validated::from_static("One"))); 190 | assert!(map.contains(&*ValidatedBuf::from_static("One"))); 191 | assert!(!map.contains(Validated::from_static("Two"))); 192 | 193 | assert!(!map.remove(Validated::from_static("Two"))); 194 | assert!(map.remove(Validated::from_static("One"))); 195 | assert!(!map.remove(Validated::from_static("One"))); 196 | 197 | assert!(map.remove(&*ValidatedBuf::from_static("Seven"))); 198 | assert!(!map.remove(Validated::from_static("Seven"))); 199 | 200 | assert!(map.is_empty()); 201 | } 202 | 203 | #[test] 204 | #[should_panic] 205 | fn verify_serialization_fail_borrow() { 206 | const SERIALIZATION: &str = "\"Test 🏗\""; 207 | dbg!(SERIALIZATION.as_bytes()); 208 | let _: &Validated = serde_json::from_str(SERIALIZATION).unwrap(); 209 | } 210 | 211 | #[test] 212 | #[should_panic] 213 | fn verify_serialization_fail_boxed() { 214 | const SERIALIZATION: &str = "\"Test 🏗\""; 215 | let _: Box = serde_json::from_str(SERIALIZATION).unwrap(); 216 | } 217 | 218 | #[test] 219 | #[should_panic] 220 | fn verify_serialization_fail_owned() { 221 | const SERIALIZATION: &str = "\"Test 🏗\""; 222 | let _: ValidatedBuf = serde_json::from_str(SERIALIZATION).unwrap(); 223 | } 224 | 225 | #[test] 226 | fn verify_serialization_pass_borrow() -> Result<(), Box> { 227 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 228 | let expected = Validated::from_str("Test \u{037E}")?; 229 | let actual: &Validated = serde_json::from_str(SERIALIZATION)?; 230 | assert_eq!(expected, actual); 231 | Ok(()) 232 | } 233 | 234 | #[test] 235 | fn verify_serialization_pass_boxed() -> Result<(), Box> { 236 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 237 | let expected = Validated::from_str("Test \u{037E}")?; 238 | let actual: Box = serde_json::from_str(SERIALIZATION)?; 239 | assert_eq!(expected, &*actual); 240 | Ok(()) 241 | } 242 | 243 | #[test] 244 | fn verify_serialization_pass_owned() -> Result<(), Box> { 245 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 246 | let expected = Validated::from_str("Test \u{037E}")?; 247 | let actual: ValidatedBuf = serde_json::from_str(SERIALIZATION)?; 248 | assert_eq!(expected, actual); 249 | Ok(()) 250 | } 251 | 252 | #[test] 253 | fn check_reference_alignment() { 254 | dbg!(std::mem::align_of::<&str>()); 255 | dbg!(std::mem::align_of::<&Validated>()); 256 | assert_eq_align!(&Validated, &str); 257 | } 258 | 259 | #[test] 260 | fn check_reference_size() { 261 | dbg!(std::mem::size_of::<&str>()); 262 | dbg!(std::mem::size_of::<&Validated>()); 263 | assert_eq_size!(&Validated, &str); 264 | } 265 | 266 | #[test] 267 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 268 | fn check_reference_size_ptr() { 269 | let s = "source"; 270 | let y: &Validated = Validated::from_str(s).unwrap(); 271 | assert_eq_size_ptr!(&s, &y); 272 | } 273 | 274 | #[test] 275 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 276 | fn check_reference_size_val() { 277 | let s = "source"; 278 | let y: &Validated = Validated::from_str(s).unwrap(); 279 | dbg!(std::mem::size_of_val(s)); 280 | dbg!(std::mem::size_of_val(y)); 281 | assert_eq_size_val!(s, y); 282 | } 283 | 284 | #[test] 285 | fn check_boxed_ref_alignment() { 286 | dbg!(std::mem::align_of::>()); 287 | dbg!(std::mem::align_of::>()); 288 | assert_eq_align!(Box, Box); 289 | } 290 | 291 | #[test] 292 | fn check_boxed_ref_size() { 293 | dbg!(std::mem::size_of::>()); 294 | dbg!(std::mem::size_of::>()); 295 | assert_eq_size!(Box, Box); 296 | } 297 | 298 | #[test] 299 | fn check_boxed_ref_size_ptr() { 300 | let source = String::from("source"); 301 | let s = source.clone().into_boxed_str(); 302 | let y: Box = ValidatedBuf::new(source).unwrap().into_boxed_ref(); 303 | assert_eq_size_ptr!(&s, &y); 304 | } 305 | 306 | #[test] 307 | fn check_boxed_ref_size_val() { 308 | let source = String::from("source"); 309 | let s = source.clone().into_boxed_str(); 310 | let y: Box = ValidatedBuf::new(source).unwrap().into_boxed_ref(); 311 | dbg!(std::mem::size_of_val(&s)); 312 | dbg!(std::mem::size_of_val(&y)); 313 | assert_eq_size_val!(s, y); 314 | } 315 | 316 | #[test] 317 | fn check_owned_alignment() { 318 | dbg!(std::mem::align_of::()); 319 | dbg!(std::mem::align_of::()); 320 | assert_eq_align!(ValidatedBuf, String); 321 | } 322 | 323 | #[test] 324 | fn check_owned_size() { 325 | dbg!(std::mem::size_of::()); 326 | dbg!(std::mem::size_of::()); 327 | assert_eq_size!(ValidatedBuf, String); 328 | } 329 | 330 | #[test] 331 | fn check_owned_size_ptr() { 332 | let s = String::from("source"); 333 | let y: ValidatedBuf = ValidatedBuf::new(s.clone()).unwrap(); 334 | assert_eq_size_ptr!(&s, &y); 335 | } 336 | 337 | #[test] 338 | fn check_owned_size_val() { 339 | let s = String::from("source"); 340 | let y: ValidatedBuf = ValidatedBuf::new(s.clone()).unwrap(); 341 | dbg!(std::mem::size_of_val(&s)); 342 | dbg!(std::mem::size_of_val(&y)); 343 | assert_eq_size_val!(s, y); 344 | } 345 | 346 | assert_core_impls!(ValidatedBuf => Validated where ValidationError = crate::InvalidData); 347 | -------------------------------------------------------------------------------- /aliri_braid/tests/common/normalized.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{BTreeSet, HashSet}, 3 | convert::TryInto, 4 | }; 5 | 6 | use quickcheck_macros::quickcheck; 7 | use static_assertions::{assert_eq_align, assert_eq_size, assert_eq_size_ptr, assert_eq_size_val}; 8 | 9 | use crate::{Normalized, NormalizedBuf}; 10 | 11 | #[test] 12 | pub fn equality_tests() -> Result<(), Box> { 13 | let x = NormalizedBuf::from_static("One Two"); 14 | let y = &*Normalized::from_str("One Two")?; 15 | assert_eq!(x, y); 16 | assert_eq!(x, *y); 17 | assert_eq!(&x, y); 18 | assert_eq!(y, x); 19 | assert_eq!(y, &x); 20 | assert_eq!(*y, x); 21 | 22 | assert_eq!("OneTwo", x.clone().take()); 23 | let z = x.clone().into_boxed_ref(); 24 | assert_eq!(y, &*z); 25 | assert_eq!(&*z, y); 26 | assert_eq!(x, &*z); 27 | assert_eq!(&*z, x); 28 | 29 | assert_eq!(x, z.into_owned()); 30 | 31 | Ok(()) 32 | } 33 | 34 | #[test] 35 | pub fn parsing_owned_pass() -> Result<(), Box> { 36 | let x: NormalizedBuf = "One".parse()?; 37 | assert_eq!("One", x.as_str()); 38 | Ok(()) 39 | } 40 | 41 | #[test] 42 | pub fn parsing_owned_non_normal_pass() -> Result<(), Box> { 43 | let x: NormalizedBuf = "One Two".parse()?; 44 | assert_eq!("OneTwo", x.as_str()); 45 | Ok(()) 46 | } 47 | 48 | #[test] 49 | #[should_panic] 50 | pub fn parsing_owned_fails() { 51 | let _: NormalizedBuf = "Test 🏗".parse().unwrap(); 52 | } 53 | 54 | #[test] 55 | pub fn try_from_owned_pass() -> Result<(), Box> { 56 | let x: NormalizedBuf = "One".try_into()?; 57 | assert_eq!("One", x.as_str()); 58 | Ok(()) 59 | } 60 | 61 | #[test] 62 | pub fn try_from_owned_non_normal_pass() -> Result<(), Box> { 63 | let x: NormalizedBuf = "One Two".try_into()?; 64 | assert_eq!("OneTwo", x.as_str()); 65 | Ok(()) 66 | } 67 | 68 | #[test] 69 | #[should_panic] 70 | pub fn try_from_owned_fails() { 71 | let _: NormalizedBuf = "Test 🏗".try_into().unwrap(); 72 | } 73 | 74 | #[test] 75 | pub fn try_from_borrowed_pass() -> Result<(), Box> { 76 | let x: &Normalized = "One".try_into()?; 77 | assert_eq!("One", x.as_str()); 78 | Ok(()) 79 | } 80 | 81 | #[test] 82 | #[should_panic] 83 | pub fn try_from_borrowed_non_normal_fails() { 84 | let _: &Normalized = "One Two".try_into().unwrap(); 85 | } 86 | 87 | #[test] 88 | #[should_panic] 89 | pub fn try_from_borrowed_fails() { 90 | let _: &Normalized = "Test 🏗".try_into().unwrap(); 91 | } 92 | 93 | #[test] 94 | fn debug_and_display_tests() { 95 | let x = NormalizedBuf::from_static("One Two"); 96 | let y = Normalized::from_str("One Two").unwrap(); 97 | let z = Normalized::from_static("OneTwo"); 98 | 99 | assert_eq!("OneTwo", x.to_string()); 100 | assert_eq!("OneTwo", y.to_string()); 101 | assert_eq!("OneTwo", z.to_string()); 102 | assert_eq!("\"OneTwo\"", format!("{:?}", x)); 103 | assert_eq!("\"OneTwo\"", format!("{:?}", y)); 104 | assert_eq!("\"OneTwo\"", format!("{:?}", z)); 105 | } 106 | 107 | #[cfg_attr(miri, ignore = "takes too long on miri")] 108 | #[quickcheck] 109 | fn owned_and_borrowed_hashes_are_equivalent(s: String) -> quickcheck::TestResult { 110 | use std::{ 111 | collections::hash_map::DefaultHasher, 112 | hash::{Hash, Hasher}, 113 | }; 114 | 115 | let owned = if let Ok(x) = NormalizedBuf::new(s.clone()) { 116 | x 117 | } else { 118 | return quickcheck::TestResult::discard(); 119 | }; 120 | 121 | let owned_hash = { 122 | let mut hasher = DefaultHasher::new(); 123 | owned.hash(&mut hasher); 124 | hasher.finish() 125 | }; 126 | 127 | let borrowed = Normalized::from_str(&s).unwrap(); 128 | 129 | let borrowed_hash = { 130 | let mut hasher = DefaultHasher::new(); 131 | borrowed.hash(&mut hasher); 132 | hasher.finish() 133 | }; 134 | 135 | if owned_hash == borrowed_hash { 136 | quickcheck::TestResult::passed() 137 | } else { 138 | quickcheck::TestResult::failed() 139 | } 140 | } 141 | 142 | #[test] 143 | fn can_use_as_hash_keys() { 144 | let mut map = HashSet::new(); 145 | 146 | assert!(map.insert(NormalizedBuf::from_static("One Two"))); 147 | assert!(map.insert(NormalizedBuf::from_static("SevenEight"))); 148 | 149 | assert!(map.contains(&*Normalized::from_str("One Two").unwrap())); 150 | assert!(map.contains(&NormalizedBuf::from_static("One Two"))); 151 | assert!(!map.contains(&*Normalized::from_str("Two Three").unwrap())); 152 | 153 | assert!(!map.remove(&*Normalized::from_str("Two Three").unwrap())); 154 | assert!(map.remove(Normalized::from_static("OneTwo"))); 155 | assert!(!map.remove(&*Normalized::from_str("One Two").unwrap())); 156 | 157 | assert!(map.remove(&NormalizedBuf::from_static("Seven Eight"))); 158 | assert!(!map.remove(Normalized::from_static("SevenEight"))); 159 | 160 | assert!(map.is_empty()); 161 | } 162 | 163 | #[test] 164 | fn can_use_refs_as_hash_keys() { 165 | let mut map = HashSet::new(); 166 | 167 | assert!(map.insert(Normalized::from_static("OneTwo"))); 168 | assert!(map.insert(Normalized::from_static("SevenEight"))); 169 | 170 | assert!(map.contains(&*Normalized::from_str("One Two").unwrap())); 171 | assert!(map.contains(&*NormalizedBuf::from_static("One Two"))); 172 | assert!(!map.contains(&*Normalized::from_str("Two Three").unwrap())); 173 | 174 | assert!(!map.remove(&*Normalized::from_str("Two Three").unwrap())); 175 | assert!(map.remove(Normalized::from_static("OneTwo"))); 176 | assert!(!map.remove(&*Normalized::from_str("One Two").unwrap())); 177 | 178 | assert!(map.remove(&*NormalizedBuf::from_static("Seven Eight"))); 179 | assert!(!map.remove(Normalized::from_static("SevenEight"))); 180 | 181 | assert!(map.is_empty()); 182 | } 183 | 184 | #[test] 185 | fn can_use_as_btree_keys() { 186 | let mut map = BTreeSet::new(); 187 | 188 | assert!(map.insert(NormalizedBuf::from_static("One Two"))); 189 | assert!(map.insert(NormalizedBuf::from_static("SevenEight"))); 190 | 191 | assert!(map.contains(&*Normalized::from_str("One Two").unwrap())); 192 | assert!(map.contains(&NormalizedBuf::from_static("One Two"))); 193 | assert!(!map.contains(&*Normalized::from_str("Two Three").unwrap())); 194 | 195 | assert!(!map.remove(&*Normalized::from_str("Two Three").unwrap())); 196 | assert!(map.remove(Normalized::from_static("OneTwo"))); 197 | assert!(!map.remove(&*Normalized::from_str("One Two").unwrap())); 198 | 199 | assert!(map.remove(&NormalizedBuf::from_static("Seven Eight"))); 200 | assert!(!map.remove(Normalized::from_static("SevenEight"))); 201 | 202 | assert!(map.is_empty()); 203 | } 204 | 205 | #[test] 206 | fn can_use_refs_as_btree_keys() { 207 | let mut map = BTreeSet::new(); 208 | 209 | assert!(map.insert(Normalized::from_static("OneTwo"))); 210 | assert!(map.insert(Normalized::from_static("SevenEight"))); 211 | 212 | assert!(map.contains(&*Normalized::from_str("One Two").unwrap())); 213 | assert!(map.contains(&*NormalizedBuf::from_static("One Two"))); 214 | assert!(!map.contains(&*Normalized::from_str("Two Three").unwrap())); 215 | 216 | assert!(!map.remove(&*Normalized::from_str("Two Three").unwrap())); 217 | assert!(map.remove(Normalized::from_static("OneTwo"))); 218 | assert!(!map.remove(&*Normalized::from_str("One Two").unwrap())); 219 | 220 | assert!(map.remove(&*NormalizedBuf::from_static("Seven Eight"))); 221 | assert!(!map.remove(Normalized::from_static("SevenEight"))); 222 | 223 | assert!(map.is_empty()); 224 | } 225 | 226 | #[test] 227 | #[should_panic] 228 | fn verify_serialization_fail_borrow() { 229 | const SERIALIZATION: &str = "\"Test 🏗\""; 230 | dbg!(SERIALIZATION.as_bytes()); 231 | let _: &Normalized = serde_json::from_str(SERIALIZATION).unwrap(); 232 | } 233 | 234 | #[test] 235 | #[should_panic] 236 | fn verify_serialization_fail_boxed() { 237 | const SERIALIZATION: &str = "\"Test 🏗\""; 238 | let _: Box = serde_json::from_str(SERIALIZATION).unwrap(); 239 | } 240 | 241 | #[test] 242 | #[should_panic] 243 | fn verify_serialization_fail_owned() { 244 | const SERIALIZATION: &str = "\"Test 🏗\""; 245 | let _: NormalizedBuf = serde_json::from_str(SERIALIZATION).unwrap(); 246 | } 247 | 248 | #[test] 249 | #[should_panic] 250 | fn verify_serialization_fail_borrow_valid_but_non_normal() { 251 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 252 | dbg!(SERIALIZATION.as_bytes()); 253 | let _: &Normalized = serde_json::from_str(SERIALIZATION).unwrap(); 254 | } 255 | 256 | #[test] 257 | fn verify_serialization_pass_boxed_valid_but_non_normal() -> Result<(), Box> 258 | { 259 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 260 | let expected = &*Normalized::from_str("Test\u{037E}")?; 261 | let actual: Box = serde_json::from_str(SERIALIZATION)?; 262 | assert_eq!(expected, &*actual); 263 | Ok(()) 264 | } 265 | 266 | #[test] 267 | fn verify_serialization_pass_owned_valid_but_non_normal() -> Result<(), Box> 268 | { 269 | const SERIALIZATION: &str = "\"Test \u{037E}\""; 270 | let expected = &*Normalized::from_str("Test\u{037E}")?; 271 | let actual: NormalizedBuf = serde_json::from_str(SERIALIZATION)?; 272 | assert_eq!(expected, actual); 273 | Ok(()) 274 | } 275 | 276 | #[test] 277 | fn verify_serialization_pass_borrow() -> Result<(), Box> { 278 | const SERIALIZATION: &str = "\"Test\u{037E}\""; 279 | let expected = &*Normalized::from_str("Test\u{037E}")?; 280 | let actual: &Normalized = serde_json::from_str(SERIALIZATION)?; 281 | assert_eq!(expected, actual); 282 | Ok(()) 283 | } 284 | 285 | #[test] 286 | fn verify_serialization_pass_boxed() -> Result<(), Box> { 287 | const SERIALIZATION: &str = "\"Test\u{037E}\""; 288 | let expected = &*Normalized::from_str("Test\u{037E}")?; 289 | let actual: Box = serde_json::from_str(SERIALIZATION)?; 290 | assert_eq!(expected, &*actual); 291 | Ok(()) 292 | } 293 | 294 | #[test] 295 | fn verify_serialization_pass_owned() -> Result<(), Box> { 296 | const SERIALIZATION: &str = "\"Test\u{037E}\""; 297 | let expected = &*Normalized::from_str("Test\u{037E}")?; 298 | let actual: NormalizedBuf = serde_json::from_str(SERIALIZATION)?; 299 | assert_eq!(expected, actual); 300 | Ok(()) 301 | } 302 | 303 | #[test] 304 | fn check_reference_alignment() { 305 | dbg!(std::mem::align_of::<&str>()); 306 | dbg!(std::mem::align_of::<&Normalized>()); 307 | assert_eq_align!(&Normalized, &str); 308 | } 309 | 310 | #[test] 311 | fn check_reference_size() { 312 | dbg!(std::mem::size_of::<&str>()); 313 | dbg!(std::mem::size_of::<&Normalized>()); 314 | assert_eq_size!(&Normalized, &str); 315 | } 316 | 317 | #[test] 318 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 319 | fn check_reference_size_ptr() { 320 | let s = "source"; 321 | let y: &Normalized = &Normalized::from_str(s).unwrap(); 322 | assert_eq_size_ptr!(&s, &y); 323 | } 324 | 325 | #[test] 326 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 327 | fn check_reference_size_ptr_normalized() { 328 | let s = "source five"; 329 | let y: &Normalized = &Normalized::from_str(s).unwrap(); 330 | assert_eq_size_ptr!(&s, &y); 331 | } 332 | 333 | #[test] 334 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 335 | fn check_reference_size_val() { 336 | let s = "source"; 337 | let y: &Normalized = &Normalized::from_str(s).unwrap(); 338 | dbg!(std::mem::size_of_val(s)); 339 | dbg!(std::mem::size_of_val(y)); 340 | assert_eq_size_val!(s, y); 341 | } 342 | 343 | #[test] 344 | #[allow(clippy::forget_ref, clippy::transmute_ptr_to_ptr)] 345 | fn check_reference_size_val_normalized() { 346 | let s = "source five"; 347 | let y: &Normalized = &Normalized::from_str(s).unwrap(); 348 | dbg!(std::mem::size_of_val(s)); 349 | dbg!(std::mem::size_of_val(y)); 350 | assert_eq_size_val!(s, y); 351 | } 352 | 353 | #[test] 354 | fn check_boxed_ref_alignment() { 355 | dbg!(std::mem::align_of::>()); 356 | dbg!(std::mem::align_of::>()); 357 | assert_eq_align!(Box, Box); 358 | } 359 | 360 | #[test] 361 | fn check_boxed_ref_size() { 362 | dbg!(std::mem::size_of::>()); 363 | dbg!(std::mem::size_of::>()); 364 | assert_eq_size!(Box, Box); 365 | } 366 | 367 | #[test] 368 | fn check_boxed_ref_size_ptr() { 369 | let source = String::from("source"); 370 | let s = source.clone().into_boxed_str(); 371 | let y: Box = NormalizedBuf::new(source).unwrap().into_boxed_ref(); 372 | assert_eq_size_ptr!(&s, &y); 373 | } 374 | 375 | #[test] 376 | fn check_boxed_ref_size_val() { 377 | let source = String::from("source"); 378 | let s = source.clone().into_boxed_str(); 379 | let y: Box = NormalizedBuf::new(source).unwrap().into_boxed_ref(); 380 | dbg!(std::mem::size_of_val(&s)); 381 | dbg!(std::mem::size_of_val(&y)); 382 | assert_eq_size_val!(s, y); 383 | } 384 | 385 | #[test] 386 | fn check_owned_alignment() { 387 | dbg!(std::mem::align_of::()); 388 | dbg!(std::mem::align_of::()); 389 | assert_eq_align!(NormalizedBuf, String); 390 | } 391 | 392 | #[test] 393 | fn check_owned_size() { 394 | dbg!(std::mem::size_of::()); 395 | dbg!(std::mem::size_of::()); 396 | assert_eq_size!(NormalizedBuf, String); 397 | } 398 | 399 | #[test] 400 | fn check_owned_size_ptr() { 401 | let s = String::from("source"); 402 | let y: NormalizedBuf = NormalizedBuf::new(s.clone()).unwrap(); 403 | assert_eq_size_ptr!(&s, &y); 404 | } 405 | 406 | #[test] 407 | fn check_owned_size_val() { 408 | let s = String::from("source"); 409 | let y: NormalizedBuf = NormalizedBuf::new(s.clone()).unwrap(); 410 | dbg!(std::mem::size_of_val(&s)); 411 | dbg!(std::mem::size_of_val(&y)); 412 | assert_eq_size_val!(s, y); 413 | } 414 | 415 | assert_core_impls!(NormalizedBuf => Normalized where NormalizationError = crate::InvalidData, ValidationError = crate::InvalidData); 416 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/impls.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens}; 2 | 3 | use super::{check_mode::CheckMode, OwnedCodeGen, RefCodeGen}; 4 | 5 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 6 | pub enum ImplOption { 7 | Implement, 8 | Omit, 9 | } 10 | 11 | impl ImplOption { 12 | fn map(self, f: F) -> Option 13 | where 14 | F: FnOnce() -> proc_macro2::TokenStream, 15 | { 16 | match self { 17 | Self::Implement => Some(f()), 18 | Self::Omit => None, 19 | } 20 | } 21 | } 22 | 23 | impl std::str::FromStr for ImplOption { 24 | type Err = &'static str; 25 | 26 | fn from_str(s: &str) -> Result { 27 | match s { 28 | "impl" => Ok(Self::Implement), 29 | "omit" => Ok(Self::Omit), 30 | _ => Err("valid values are: `impl` or `omit`"), 31 | } 32 | } 33 | } 34 | 35 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 36 | pub enum DelegatingImplOption { 37 | Implement, 38 | OwnedOnly, 39 | Omit, 40 | } 41 | 42 | impl DelegatingImplOption { 43 | fn map_owned(self, f: F) -> Option 44 | where 45 | F: FnOnce() -> proc_macro2::TokenStream, 46 | { 47 | match self { 48 | Self::Implement | Self::OwnedOnly => Some(f()), 49 | Self::Omit => None, 50 | } 51 | } 52 | 53 | fn map_ref(self, f: F) -> Option 54 | where 55 | F: FnOnce() -> proc_macro2::TokenStream, 56 | { 57 | match self { 58 | Self::Implement => Some(f()), 59 | Self::Omit | Self::OwnedOnly => None, 60 | } 61 | } 62 | } 63 | 64 | impl std::str::FromStr for DelegatingImplOption { 65 | type Err = &'static str; 66 | 67 | fn from_str(s: &str) -> Result { 68 | match s { 69 | "impl" => Ok(Self::Implement), 70 | "owned" => Ok(Self::OwnedOnly), 71 | "omit" => Ok(Self::Omit), 72 | _ => Err("valid values are: `impl`, `owned`, or `omit`"), 73 | } 74 | } 75 | } 76 | 77 | impl From for DelegatingImplOption { 78 | fn from(opt: ImplOption) -> Self { 79 | match opt { 80 | ImplOption::Implement => Self::Implement, 81 | ImplOption::Omit => Self::Omit, 82 | } 83 | } 84 | } 85 | 86 | #[derive(Debug, Default)] 87 | pub struct Impls { 88 | pub clone: ImplClone, 89 | pub debug: ImplDebug, 90 | pub display: ImplDisplay, 91 | pub ord: ImplOrd, 92 | pub serde: ImplSerde, 93 | } 94 | 95 | pub(crate) trait ToImpl { 96 | fn to_owned_impl(&self, _gen: &OwnedCodeGen) -> Option { 97 | None 98 | } 99 | 100 | fn to_borrowed_impl(&self, _gen: &RefCodeGen) -> Option { 101 | None 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct ImplClone(ImplOption); 107 | 108 | impl Default for ImplClone { 109 | fn default() -> Self { 110 | Self(ImplOption::Implement) 111 | } 112 | } 113 | 114 | impl From for ImplClone { 115 | fn from(opt: ImplOption) -> Self { 116 | Self(opt) 117 | } 118 | } 119 | 120 | impl ToImpl for ImplClone { 121 | fn to_owned_impl(&self, _gen: &OwnedCodeGen) -> Option { 122 | self.0.map(|| quote! { #[derive(Clone)] }) 123 | } 124 | } 125 | 126 | #[derive(Debug)] 127 | pub struct ImplDisplay(DelegatingImplOption); 128 | 129 | impl Default for ImplDisplay { 130 | fn default() -> Self { 131 | Self(DelegatingImplOption::Implement) 132 | } 133 | } 134 | 135 | impl From for ImplDisplay { 136 | fn from(opt: DelegatingImplOption) -> Self { 137 | Self(opt) 138 | } 139 | } 140 | impl ToImpl for ImplDisplay { 141 | fn to_owned_impl(&self, gen: &OwnedCodeGen) -> Option { 142 | let ty = gen.ty; 143 | let ref_ty = gen.ref_ty; 144 | let core = gen.std_lib.core(); 145 | self.0.map_owned(|| { 146 | quote! { 147 | #[automatically_derived] 148 | impl ::#core::fmt::Display for #ty { 149 | #[inline] 150 | fn fmt(&self, f: &mut ::#core::fmt::Formatter) -> ::#core::fmt::Result { 151 | <#ref_ty as ::#core::fmt::Display>::fmt(::#core::ops::Deref::deref(self), f) 152 | } 153 | } 154 | } 155 | }) 156 | } 157 | 158 | fn to_borrowed_impl(&self, gen: &RefCodeGen) -> Option { 159 | let ty = &gen.ty; 160 | let field_name = &gen.field.name; 161 | let core = gen.std_lib.core(); 162 | self.0.map_ref(|| { 163 | quote! { 164 | #[automatically_derived] 165 | impl ::#core::fmt::Display for #ty { 166 | #[inline] 167 | fn fmt(&self, f: &mut ::#core::fmt::Formatter) -> ::#core::fmt::Result { 168 | ::fmt(&self.#field_name, f) 169 | } 170 | } 171 | } 172 | }) 173 | } 174 | } 175 | 176 | #[derive(Debug)] 177 | pub struct ImplDebug(DelegatingImplOption); 178 | 179 | impl Default for ImplDebug { 180 | fn default() -> Self { 181 | Self(DelegatingImplOption::Implement) 182 | } 183 | } 184 | 185 | impl From for ImplDebug { 186 | fn from(opt: DelegatingImplOption) -> Self { 187 | Self(opt) 188 | } 189 | } 190 | 191 | impl ToImpl for ImplDebug { 192 | fn to_owned_impl(&self, gen: &OwnedCodeGen) -> Option { 193 | let ty = gen.ty; 194 | let ref_ty = gen.ref_ty; 195 | let core = gen.std_lib.core(); 196 | self.0.map_owned(|| { 197 | quote! { 198 | #[automatically_derived] 199 | impl ::#core::fmt::Debug for #ty { 200 | #[inline] 201 | fn fmt(&self, f: &mut ::#core::fmt::Formatter) -> ::#core::fmt::Result { 202 | <#ref_ty as ::#core::fmt::Debug>::fmt(::#core::ops::Deref::deref(self), f) 203 | } 204 | } 205 | } 206 | }) 207 | } 208 | 209 | fn to_borrowed_impl(&self, gen: &RefCodeGen) -> Option { 210 | let ty = &gen.ty; 211 | let field_name = &gen.field.name; 212 | let core = gen.std_lib.core(); 213 | self.0.map_ref(|| { 214 | quote! { 215 | #[automatically_derived] 216 | impl ::#core::fmt::Debug for #ty { 217 | #[inline] 218 | fn fmt(&self, f: &mut ::#core::fmt::Formatter) -> ::#core::fmt::Result { 219 | ::fmt(&self.#field_name, f) 220 | } 221 | } 222 | } 223 | }) 224 | } 225 | } 226 | 227 | #[derive(Debug)] 228 | pub struct ImplOrd(DelegatingImplOption); 229 | 230 | impl Default for ImplOrd { 231 | fn default() -> Self { 232 | Self(DelegatingImplOption::Implement) 233 | } 234 | } 235 | 236 | impl From for ImplOrd { 237 | fn from(opt: DelegatingImplOption) -> Self { 238 | Self(opt) 239 | } 240 | } 241 | 242 | impl ToImpl for ImplOrd { 243 | fn to_owned_impl(&self, gen: &OwnedCodeGen) -> Option { 244 | let ty = &gen.ty; 245 | let field_name = &gen.field.name; 246 | let core = gen.std_lib.core(); 247 | self.0.map_owned(|| quote! { 248 | #[automatically_derived] 249 | impl ::#core::cmp::Ord for #ty { 250 | #[inline] 251 | fn cmp(&self, other: &Self) -> ::#core::cmp::Ordering { 252 | ::#core::cmp::Ord::cmp(&self.#field_name, &other.#field_name) 253 | } 254 | } 255 | 256 | #[automatically_derived] 257 | impl ::#core::cmp::PartialOrd for #ty { 258 | #[inline] 259 | fn partial_cmp(&self, other: &Self) -> ::#core::option::Option<::#core::cmp::Ordering> { 260 | ::#core::cmp::PartialOrd::partial_cmp(&self.#field_name, &other.#field_name) 261 | } 262 | } 263 | }) 264 | } 265 | 266 | fn to_borrowed_impl(&self, _gen: &RefCodeGen) -> Option { 267 | self.0.map_ref(|| quote! { #[derive(PartialOrd, Ord)] }) 268 | } 269 | } 270 | 271 | #[derive(Debug)] 272 | pub struct ImplSerde(ImplOption); 273 | 274 | impl Default for ImplSerde { 275 | fn default() -> Self { 276 | Self(ImplOption::Omit) 277 | } 278 | } 279 | 280 | impl From for ImplSerde { 281 | fn from(opt: ImplOption) -> Self { 282 | Self(opt) 283 | } 284 | } 285 | 286 | impl ToImpl for ImplSerde { 287 | fn to_owned_impl(&self, gen: &OwnedCodeGen) -> Option { 288 | self.0.map(|| { 289 | let handle_failure = gen.check_mode.serde_err_handler(); 290 | 291 | let name = gen.ty; 292 | let field_name = &gen.field.name; 293 | let wrapped_type = &gen.field.ty; 294 | 295 | quote! { 296 | #[automatically_derived] 297 | impl ::serde::Serialize for #name { 298 | fn serialize(&self, serializer: S) -> Result { 299 | <#wrapped_type as ::serde::Serialize>::serialize(&self.#field_name, serializer) 300 | } 301 | } 302 | 303 | #[allow(clippy::needless_question_mark, clippy::unsafe_derive_deserialize)] 304 | #[automatically_derived] 305 | impl<'de> ::serde::Deserialize<'de> for #name { 306 | fn deserialize>(deserializer: D) -> Result { 307 | let raw = <#wrapped_type as ::serde::Deserialize<'de>>::deserialize(deserializer)?; 308 | Ok(Self::new(raw)#handle_failure) 309 | } 310 | } 311 | } 312 | }) 313 | } 314 | 315 | fn to_borrowed_impl(&self, gen: &RefCodeGen) -> Option { 316 | self.0.map(|| { 317 | let ty = &gen.ty; 318 | let check_mode = gen.check_mode; 319 | let core = gen.std_lib.core(); 320 | let alloc = gen.std_lib.alloc(); 321 | 322 | let handle_failure = check_mode.serde_err_handler(); 323 | 324 | let deserialize_boxed = gen.owned_ty.map(|owned_ty| { 325 | quote! { 326 | #[automatically_derived] 327 | impl<'de> ::serde::Deserialize<'de> for ::#alloc::boxed::Box<#ty> { 328 | fn deserialize>(deserializer: D) -> ::#core::result::Result { 329 | let owned = <#owned_ty as ::serde::Deserialize<'de>>::deserialize(deserializer)?; 330 | ::#core::result::Result::Ok(owned.into_boxed_ref()) 331 | } 332 | } 333 | } 334 | }); 335 | 336 | let deserialize = if matches!(check_mode, CheckMode::Normalize(_)) { 337 | let deserialize_doc = format!( 338 | "Deserializes a `{ty}` in normalized form\n\ 339 | \n\ 340 | This deserializer _requires_ that the value already be in normalized form. \ 341 | If values may require normalization, then deserialized as [`{owned}`] or \ 342 | [`Cow<{ty}>`][{alloc}::borrow::Cow] instead.", 343 | ty = ty.to_token_stream(), 344 | owned = gen.owned_ty.expect("normalize not available if no owned").to_token_stream(), 345 | ); 346 | 347 | quote! { 348 | // impl<'de: 'a, 'a> ::serde::Deserialize<'de> for ::#alloc::borrow::Cow<'a, #name> { 349 | // fn deserialize>(deserializer: D) -> ::#core::result::Result { 350 | // let raw = <&str as ::serde::Deserialize<'de>>::deserialize(deserializer)?; 351 | // ::#core::result::Result::Ok(#name::from_str(raw)#handle_failure) 352 | // } 353 | // } 354 | // 355 | #[doc = #deserialize_doc] 356 | #[allow(clippy::needless_question_mark, clippy::unsafe_derive_deserialize)] 357 | #[automatically_derived] 358 | impl<'de: 'a, 'a> ::serde::Deserialize<'de> for &'a #ty { 359 | fn deserialize>(deserializer: D) -> ::#core::result::Result { 360 | let raw = <&str as ::serde::Deserialize<'de>>::deserialize(deserializer)?; 361 | ::#core::result::Result::Ok(#ty::from_normalized_str(raw)#handle_failure) 362 | } 363 | } 364 | } 365 | } else { 366 | quote! { 367 | #[allow(clippy::needless_question_mark, clippy::unsafe_derive_deserialize)] 368 | #[automatically_derived] 369 | impl<'de: 'a, 'a> ::serde::Deserialize<'de> for &'a #ty { 370 | fn deserialize>(deserializer: D) -> ::#core::result::Result { 371 | let raw = <&str as ::serde::Deserialize<'de>>::deserialize(deserializer)?; 372 | ::#core::result::Result::Ok(#ty::from_str(raw)#handle_failure) 373 | } 374 | } 375 | } 376 | }; 377 | 378 | quote! { 379 | #[automatically_derived] 380 | impl ::serde::Serialize for #ty { 381 | fn serialize(&self, serializer: S) -> ::#core::result::Result { 382 | ::serialize(self.as_str(), serializer) 383 | } 384 | } 385 | 386 | #deserialize 387 | #deserialize_boxed 388 | } 389 | }) 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | use quote::{format_ident, ToTokens, TokenStreamExt}; 2 | use symbol::{parse_expr_as_lit, parse_lit_into_string, parse_lit_into_type}; 3 | use syn::spanned::Spanned; 4 | 5 | pub use self::{borrowed::RefCodeGen, owned::OwnedCodeGen}; 6 | use self::{ 7 | check_mode::{CheckMode, IndefiniteCheckMode}, 8 | impls::{DelegatingImplOption, ImplOption, Impls}, 9 | }; 10 | 11 | mod borrowed; 12 | mod check_mode; 13 | mod impls; 14 | mod owned; 15 | mod symbol; 16 | 17 | pub type AttrList = syn::punctuated::Punctuated; 18 | 19 | #[derive(Clone, Debug)] 20 | pub struct StdLib { 21 | core: proc_macro2::Ident, 22 | alloc: proc_macro2::Ident, 23 | } 24 | 25 | impl StdLib { 26 | pub fn no_std(span: proc_macro2::Span) -> Self { 27 | Self { 28 | core: proc_macro2::Ident::new("core", span), 29 | alloc: proc_macro2::Ident::new("alloc", span), 30 | } 31 | } 32 | 33 | pub fn core(&self) -> &proc_macro2::Ident { 34 | &self.core 35 | } 36 | 37 | pub fn alloc(&self) -> &proc_macro2::Ident { 38 | &self.alloc 39 | } 40 | } 41 | 42 | impl Default for StdLib { 43 | fn default() -> Self { 44 | Self { 45 | core: proc_macro2::Ident::new("std", proc_macro2::Span::call_site()), 46 | alloc: proc_macro2::Ident::new("std", proc_macro2::Span::call_site()), 47 | } 48 | } 49 | } 50 | 51 | pub struct Params { 52 | ref_ty: Option, 53 | ref_doc: Vec, 54 | ref_attrs: AttrList, 55 | owned_attrs: AttrList, 56 | std_lib: StdLib, 57 | check_mode: IndefiniteCheckMode, 58 | expose_inner: bool, 59 | impls: Impls, 60 | } 61 | 62 | impl Default for Params { 63 | fn default() -> Self { 64 | Self { 65 | ref_ty: None, 66 | ref_doc: Vec::new(), 67 | ref_attrs: AttrList::new(), 68 | owned_attrs: AttrList::new(), 69 | std_lib: StdLib::default(), 70 | check_mode: IndefiniteCheckMode::None, 71 | expose_inner: true, 72 | impls: Impls::default(), 73 | } 74 | } 75 | } 76 | 77 | impl syn::parse::Parse for Params { 78 | fn parse(input: syn::parse::ParseStream) -> Result { 79 | let mut params = Self::default(); 80 | let args = 81 | syn::punctuated::Punctuated::::parse_terminated(input)?; 82 | 83 | for arg in args { 84 | match &arg { 85 | syn::Meta::NameValue(nv) if nv.path == symbol::REF => { 86 | params.ref_ty = Some(parse_lit_into_type( 87 | symbol::REF, 88 | parse_expr_as_lit(&nv.value)?, 89 | )?); 90 | } 91 | syn::Meta::NameValue(nv) if nv.path == symbol::VALIDATOR => { 92 | let validator = 93 | parse_lit_into_type(symbol::VALIDATOR, parse_expr_as_lit(&nv.value)?)?; 94 | params 95 | .check_mode 96 | .try_set_validator(Some(validator)) 97 | .map_err(|s| syn::Error::new_spanned(nv, s))?; 98 | } 99 | syn::Meta::NameValue(nv) if nv.path == symbol::NORMALIZER => { 100 | let normalizer = 101 | parse_lit_into_type(symbol::NORMALIZER, parse_expr_as_lit(&nv.value)?)?; 102 | params 103 | .check_mode 104 | .try_set_normalizer(Some(normalizer)) 105 | .map_err(|s| syn::Error::new_spanned(nv, s))?; 106 | } 107 | syn::Meta::NameValue(nv) if nv.path == symbol::REF_DOC => { 108 | params 109 | .ref_doc 110 | .push(parse_expr_as_lit(&nv.value)?.to_owned()); 111 | } 112 | syn::Meta::List(nv) if nv.path == symbol::REF_ATTR => { 113 | params.ref_attrs.extend(nv.parse_args::()); 114 | } 115 | syn::Meta::List(nv) if nv.path == symbol::OWNED_ATTR => { 116 | params.owned_attrs.extend(nv.parse_args::()); 117 | } 118 | syn::Meta::NameValue(nv) if nv.path == symbol::DEBUG => { 119 | params.impls.debug = 120 | parse_lit_into_string(symbol::DEBUG, parse_expr_as_lit(&nv.value)?)? 121 | .parse::() 122 | .map_err(|e| syn::Error::new_spanned(&arg, e.to_owned()))? 123 | .into(); 124 | } 125 | syn::Meta::NameValue(nv) if nv.path == symbol::DISPLAY => { 126 | params.impls.display = 127 | parse_lit_into_string(symbol::DISPLAY, parse_expr_as_lit(&nv.value)?)? 128 | .parse::() 129 | .map_err(|e| syn::Error::new_spanned(&arg, e.to_owned()))? 130 | .into(); 131 | } 132 | syn::Meta::NameValue(nv) if nv.path == symbol::ORD => { 133 | params.impls.ord = 134 | parse_lit_into_string(symbol::ORD, parse_expr_as_lit(&nv.value)?)? 135 | .parse::() 136 | .map_err(|e| syn::Error::new_spanned(&arg, e.to_owned()))? 137 | .into(); 138 | } 139 | syn::Meta::NameValue(nv) if nv.path == symbol::CLONE => { 140 | params.impls.clone = 141 | parse_lit_into_string(symbol::CLONE, parse_expr_as_lit(&nv.value)?)? 142 | .parse::() 143 | .map_err(|e| syn::Error::new_spanned(&arg, e.to_owned()))? 144 | .into(); 145 | } 146 | syn::Meta::NameValue(nv) if nv.path == symbol::SERDE => { 147 | params.impls.serde = 148 | parse_lit_into_string(symbol::SERDE, parse_expr_as_lit(&nv.value)?)? 149 | .parse::() 150 | .map_err(|e| syn::Error::new_spanned(&arg, e.to_owned()))? 151 | .into(); 152 | } 153 | syn::Meta::Path(p) if p == symbol::SERDE => { 154 | params.impls.serde = ImplOption::Implement.into(); 155 | } 156 | syn::Meta::Path(p) if p == symbol::VALIDATOR => { 157 | params 158 | .check_mode 159 | .try_set_validator(None) 160 | .map_err(|s| syn::Error::new_spanned(p, s))?; 161 | } 162 | syn::Meta::Path(p) if p == symbol::NORMALIZER => { 163 | params 164 | .check_mode 165 | .try_set_normalizer(None) 166 | .map_err(|s| syn::Error::new_spanned(p, s))?; 167 | } 168 | syn::Meta::Path(p) if p == symbol::NO_STD => { 169 | params.std_lib = StdLib::no_std(p.span()); 170 | } 171 | syn::Meta::Path(p) if p == symbol::NO_EXPOSE => { 172 | params.expose_inner = false; 173 | } 174 | syn::Meta::Path(ref path) 175 | | syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => { 176 | return Err(syn::Error::new_spanned( 177 | &arg, 178 | format!("unsupported argument `{}`", path.to_token_stream()), 179 | )); 180 | } 181 | _ => { 182 | return Err(syn::Error::new_spanned( 183 | &arg, 184 | "unsupported argument".to_string(), 185 | )); 186 | } 187 | } 188 | } 189 | 190 | Ok(params) 191 | } 192 | } 193 | 194 | impl Params { 195 | pub fn build(self, mut body: syn::ItemStruct) -> Result { 196 | let Params { 197 | ref_ty, 198 | ref_doc, 199 | ref_attrs, 200 | owned_attrs, 201 | std_lib, 202 | check_mode, 203 | expose_inner, 204 | impls, 205 | } = self; 206 | 207 | create_field_if_none(&mut body.fields); 208 | let (wrapped_type, field_ident, field_attrs) = get_field_info(&body.fields)?; 209 | let owned_ty = &body.ident; 210 | let ref_ty = ref_ty.unwrap_or_else(|| infer_ref_type_from_owned_name(owned_ty)); 211 | let check_mode = check_mode.infer_validator_if_missing(owned_ty); 212 | let field = Field { 213 | attrs: field_attrs.to_owned(), 214 | name: field_ident 215 | .cloned() 216 | .map_or(FieldName::Unnamed, FieldName::Named), 217 | ty: wrapped_type.to_owned(), 218 | }; 219 | 220 | Ok(CodeGen { 221 | check_mode, 222 | body, 223 | field, 224 | 225 | owned_attrs, 226 | 227 | ref_doc, 228 | ref_attrs, 229 | ref_ty, 230 | 231 | std_lib, 232 | expose_inner, 233 | impls, 234 | }) 235 | } 236 | } 237 | 238 | pub struct ParamsRef { 239 | std_lib: StdLib, 240 | check_mode: IndefiniteCheckMode, 241 | impls: Impls, 242 | } 243 | 244 | impl Default for ParamsRef { 245 | fn default() -> Self { 246 | Self { 247 | std_lib: StdLib::default(), 248 | check_mode: IndefiniteCheckMode::None, 249 | impls: Impls::default(), 250 | } 251 | } 252 | } 253 | 254 | impl syn::parse::Parse for ParamsRef { 255 | fn parse(input: syn::parse::ParseStream) -> Result { 256 | let mut params = Self::default(); 257 | let args = 258 | syn::punctuated::Punctuated::::parse_terminated(input)?; 259 | 260 | for arg in args { 261 | match arg { 262 | syn::Meta::NameValue(nv) if nv.path == symbol::VALIDATOR => { 263 | let validator = 264 | parse_lit_into_type(symbol::VALIDATOR, parse_expr_as_lit(&nv.value)?)?; 265 | params 266 | .check_mode 267 | .try_set_validator(Some(validator)) 268 | .map_err(|s| syn::Error::new_spanned(nv, s))?; 269 | } 270 | syn::Meta::NameValue(nv) if nv.path == symbol::DEBUG => { 271 | params.impls.debug = 272 | parse_lit_into_string(symbol::DEBUG, parse_expr_as_lit(&nv.value)?)? 273 | .parse::() 274 | .map_err(|e| syn::Error::new_spanned(nv, e.to_owned())) 275 | .map(DelegatingImplOption::from)? 276 | .into(); 277 | } 278 | syn::Meta::NameValue(nv) if nv.path == symbol::DISPLAY => { 279 | params.impls.display = 280 | parse_lit_into_string(symbol::DISPLAY, parse_expr_as_lit(&nv.value)?)? 281 | .parse::() 282 | .map_err(|e| syn::Error::new_spanned(nv, e.to_owned())) 283 | .map(DelegatingImplOption::from)? 284 | .into(); 285 | } 286 | syn::Meta::NameValue(nv) if nv.path == symbol::ORD => { 287 | params.impls.ord = 288 | parse_lit_into_string(symbol::ORD, parse_expr_as_lit(&nv.value)?)? 289 | .parse::() 290 | .map_err(|e| syn::Error::new_spanned(nv, e.to_owned())) 291 | .map(DelegatingImplOption::from)? 292 | .into(); 293 | } 294 | syn::Meta::NameValue(nv) if nv.path == symbol::SERDE => { 295 | params.impls.serde = 296 | parse_lit_into_string(symbol::SERDE, parse_expr_as_lit(&nv.value)?)? 297 | .parse::() 298 | .map_err(|e| syn::Error::new_spanned(nv, e.to_owned()))? 299 | .into(); 300 | } 301 | syn::Meta::Path(p) if p == symbol::SERDE => { 302 | params.impls.serde = ImplOption::Implement.into(); 303 | } 304 | syn::Meta::Path(p) if p == symbol::VALIDATOR => { 305 | params 306 | .check_mode 307 | .try_set_validator(None) 308 | .map_err(|s| syn::Error::new_spanned(p, s))?; 309 | } 310 | syn::Meta::Path(p) if p == symbol::NO_STD => { 311 | params.std_lib = StdLib::no_std(p.span()); 312 | } 313 | syn::Meta::Path(ref path) 314 | | syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) => { 315 | return Err(syn::Error::new_spanned( 316 | &arg, 317 | format!("unsupported argument `{}`", path.to_token_stream()), 318 | )); 319 | } 320 | _ => { 321 | return Err(syn::Error::new_spanned( 322 | &arg, 323 | "unsupported argument".to_string(), 324 | )); 325 | } 326 | } 327 | } 328 | 329 | Ok(params) 330 | } 331 | } 332 | 333 | impl ParamsRef { 334 | pub fn build(self, body: &mut syn::ItemStruct) -> Result { 335 | let ParamsRef { 336 | std_lib, 337 | check_mode, 338 | impls, 339 | } = self; 340 | 341 | create_ref_field_if_none(&mut body.fields); 342 | let (wrapped_type, field_ident, field_attrs) = get_field_info(&body.fields)?; 343 | let ref_ty = &body.ident; 344 | let check_mode = check_mode.infer_validator_if_missing(ref_ty); 345 | let field = Field { 346 | attrs: field_attrs.to_owned(), 347 | name: field_ident 348 | .cloned() 349 | .map_or(FieldName::Unnamed, FieldName::Named), 350 | ty: wrapped_type.to_owned(), 351 | }; 352 | 353 | let code_gen = RefCodeGen { 354 | doc: &[], 355 | common_attrs: &body.attrs, 356 | attrs: &syn::punctuated::Punctuated::default(), 357 | vis: &body.vis, 358 | ty: &syn::Type::Verbatim(body.ident.to_token_stream()), 359 | ident: body.ident.clone(), 360 | field, 361 | check_mode: &check_mode, 362 | owned_ty: None, 363 | std_lib: &std_lib, 364 | impls: &impls, 365 | } 366 | .tokens(); 367 | 368 | Ok(code_gen) 369 | } 370 | } 371 | 372 | pub struct CodeGen { 373 | check_mode: CheckMode, 374 | body: syn::ItemStruct, 375 | field: Field, 376 | 377 | owned_attrs: AttrList, 378 | 379 | ref_doc: Vec, 380 | ref_attrs: AttrList, 381 | ref_ty: syn::Type, 382 | 383 | std_lib: StdLib, 384 | expose_inner: bool, 385 | impls: Impls, 386 | } 387 | 388 | impl CodeGen { 389 | pub fn generate(&self) -> proc_macro2::TokenStream { 390 | let owned = self.owned().tokens(); 391 | let ref_ = self.borrowed().tokens(); 392 | 393 | quote::quote! { 394 | #owned 395 | #ref_ 396 | } 397 | } 398 | 399 | pub fn owned(&self) -> OwnedCodeGen { 400 | OwnedCodeGen { 401 | common_attrs: &self.body.attrs, 402 | check_mode: &self.check_mode, 403 | body: &self.body, 404 | field: &self.field, 405 | attrs: &self.owned_attrs, 406 | ty: &self.body.ident, 407 | ref_ty: &self.ref_ty, 408 | std_lib: &self.std_lib, 409 | expose_inner: self.expose_inner, 410 | impls: &self.impls, 411 | } 412 | } 413 | 414 | pub fn borrowed(&self) -> RefCodeGen { 415 | RefCodeGen { 416 | doc: &self.ref_doc, 417 | common_attrs: &self.body.attrs, 418 | check_mode: &self.check_mode, 419 | vis: &self.body.vis, 420 | field: self.field.clone(), 421 | attrs: &self.ref_attrs, 422 | ty: &self.ref_ty, 423 | ident: syn::Ident::new( 424 | &self.ref_ty.to_token_stream().to_string(), 425 | self.ref_ty.span(), 426 | ), 427 | owned_ty: Some(&self.body.ident), 428 | std_lib: &self.std_lib, 429 | impls: &self.impls, 430 | } 431 | } 432 | } 433 | 434 | fn infer_ref_type_from_owned_name(name: &syn::Ident) -> syn::Type { 435 | let name_str = name.to_string(); 436 | if name_str.ends_with("Buf") || name_str.ends_with("String") { 437 | syn::Type::Path(syn::TypePath { 438 | qself: None, 439 | path: syn::Path::from(format_ident!("{}", name_str[..name_str.len() - 3])), 440 | }) 441 | } else { 442 | syn::Type::Path(syn::TypePath { 443 | qself: None, 444 | path: syn::Path::from(format_ident!("{}Ref", name_str)), 445 | }) 446 | } 447 | } 448 | 449 | fn create_field_if_none(fields: &mut syn::Fields) { 450 | if fields.is_empty() { 451 | let field = syn::Field { 452 | vis: syn::Visibility::Inherited, 453 | attrs: Vec::new(), 454 | colon_token: None, 455 | ident: None, 456 | ty: syn::Type::Verbatim( 457 | syn::Ident::new("String", proc_macro2::Span::call_site()).into_token_stream(), 458 | ), 459 | mutability: syn::FieldMutability::None, 460 | }; 461 | 462 | *fields = syn::Fields::Unnamed(syn::FieldsUnnamed { 463 | paren_token: syn::token::Paren::default(), 464 | unnamed: std::iter::once(field).collect(), 465 | }); 466 | } 467 | } 468 | 469 | fn create_ref_field_if_none(fields: &mut syn::Fields) { 470 | if fields.is_empty() { 471 | let field = syn::Field { 472 | vis: syn::Visibility::Inherited, 473 | attrs: Vec::new(), 474 | colon_token: None, 475 | ident: None, 476 | ty: syn::Type::Verbatim( 477 | syn::Ident::new("str", proc_macro2::Span::call_site()).into_token_stream(), 478 | ), 479 | mutability: syn::FieldMutability::None, 480 | }; 481 | 482 | *fields = syn::Fields::Unnamed(syn::FieldsUnnamed { 483 | paren_token: syn::token::Paren::default(), 484 | unnamed: std::iter::once(field).collect(), 485 | }); 486 | } 487 | } 488 | 489 | fn get_field_info( 490 | fields: &syn::Fields, 491 | ) -> Result<(&syn::Type, Option<&syn::Ident>, &[syn::Attribute]), syn::Error> { 492 | let mut iter = fields.iter(); 493 | let field = iter.next().unwrap(); 494 | 495 | if iter.next().is_some() { 496 | return Err(syn::Error::new_spanned( 497 | fields, 498 | "typed string can only have one field", 499 | )); 500 | } 501 | 502 | Ok((&field.ty, field.ident.as_ref(), &field.attrs)) 503 | } 504 | 505 | #[derive(Clone)] 506 | pub struct Field { 507 | pub attrs: Vec, 508 | pub name: FieldName, 509 | pub ty: syn::Type, 510 | } 511 | 512 | impl Field { 513 | fn self_constructor(&self) -> SelfConstructorImpl { 514 | SelfConstructorImpl(self) 515 | } 516 | } 517 | 518 | #[derive(Clone)] 519 | pub enum FieldName { 520 | Named(syn::Ident), 521 | Unnamed, 522 | } 523 | 524 | impl FieldName { 525 | fn constructor_delimiter(&self) -> proc_macro2::Delimiter { 526 | match self { 527 | FieldName::Named(_) => proc_macro2::Delimiter::Brace, 528 | FieldName::Unnamed => proc_macro2::Delimiter::Parenthesis, 529 | } 530 | } 531 | 532 | fn input_name(&self) -> proc_macro2::Ident { 533 | match self { 534 | FieldName::Named(name) => name.clone(), 535 | FieldName::Unnamed => proc_macro2::Ident::new("raw", proc_macro2::Span::call_site()), 536 | } 537 | } 538 | } 539 | 540 | impl ToTokens for FieldName { 541 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 542 | match self { 543 | Self::Named(ident) => ident.to_tokens(tokens), 544 | Self::Unnamed => tokens.append(proc_macro2::Literal::u8_unsuffixed(0)), 545 | } 546 | } 547 | } 548 | 549 | struct SelfConstructorImpl<'a>(&'a Field); 550 | 551 | impl<'a> ToTokens for SelfConstructorImpl<'a> { 552 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 553 | let Self(field) = self; 554 | tokens.append(proc_macro2::Ident::new( 555 | "Self", 556 | proc_macro2::Span::call_site(), 557 | )); 558 | tokens.append(proc_macro2::Group::new( 559 | field.name.constructor_delimiter(), 560 | field.name.input_name().into_token_stream(), 561 | )); 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/owned.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens}; 2 | 3 | use super::{impls::ToImpl, AttrList, CheckMode, Field, Impls, StdLib}; 4 | 5 | pub struct OwnedCodeGen<'a> { 6 | pub common_attrs: &'a [syn::Attribute], 7 | pub attrs: &'a AttrList, 8 | pub body: &'a syn::ItemStruct, 9 | pub ty: &'a syn::Ident, 10 | pub field: &'a Field, 11 | pub check_mode: &'a CheckMode, 12 | pub ref_ty: &'a syn::Type, 13 | pub std_lib: &'a StdLib, 14 | pub expose_inner: bool, 15 | pub impls: &'a Impls, 16 | } 17 | 18 | impl<'a> OwnedCodeGen<'a> { 19 | fn constructor(&self) -> proc_macro2::TokenStream { 20 | match &self.check_mode { 21 | CheckMode::None => self.infallible_constructor(), 22 | CheckMode::Validate(validator) => self.fallible_constructor(validator), 23 | CheckMode::Normalize(normalizer) => self.normalized_constructor(normalizer), 24 | } 25 | } 26 | 27 | fn infallible_constructor(&self) -> proc_macro2::TokenStream { 28 | let doc_comment = format!("Constructs a new {}", self.ty); 29 | let static_doc_comment = format!("{doc_comment} from a static reference"); 30 | 31 | let param = self.field.name.input_name(); 32 | let create = self.field.self_constructor(); 33 | let ref_ty = self.ref_ty; 34 | let field_ty = &self.field.ty; 35 | let alloc = self.std_lib.alloc(); 36 | 37 | let vis = self 38 | .expose_inner 39 | .then(|| proc_macro2::Ident::new("pub", proc_macro2::Span::call_site())); 40 | 41 | quote! { 42 | #[doc = #doc_comment] 43 | #[inline] 44 | #vis const fn new(#param: #field_ty) -> Self { 45 | #create 46 | } 47 | 48 | #[inline] 49 | #[doc = #static_doc_comment] 50 | #[track_caller] 51 | pub fn from_static(raw: &'static str) -> Self { 52 | ::#alloc::borrow::ToOwned::to_owned(#ref_ty::from_static(raw)) 53 | } 54 | } 55 | } 56 | 57 | fn fallible_constructor(&self, validator: &syn::Type) -> proc_macro2::TokenStream { 58 | let validator_tokens = validator.to_token_stream(); 59 | let doc_comment = format!( 60 | "Constructs a new {} if it conforms to [`{}`]", 61 | self.ty, validator_tokens 62 | ); 63 | 64 | let static_doc_comment = format!( 65 | "Constructs a new {} from a static reference if it conforms to [`{}`]", 66 | self.ty, validator_tokens 67 | ); 68 | 69 | let doc_comment_unsafe = format!( 70 | "Constructs a new {} without validation\n\n# Safety\n\nConsumers of this function \ 71 | must ensure that values conform to [`{}`]. Failure to maintain this invariant may \ 72 | lead to undefined behavior.", 73 | self.ty, validator_tokens 74 | ); 75 | 76 | let validator = crate::as_validator(validator); 77 | let param = self.field.name.input_name(); 78 | let create = self.field.self_constructor(); 79 | let ref_ty = self.ref_ty; 80 | let field_ty = &self.field.ty; 81 | let core = self.std_lib.core(); 82 | let alloc = self.std_lib.alloc(); 83 | 84 | let vis = self 85 | .expose_inner 86 | .then(|| proc_macro2::Ident::new("pub", proc_macro2::Span::call_site())); 87 | 88 | quote! { 89 | #[doc = #doc_comment] 90 | #[inline] 91 | #vis fn new(#param: #field_ty) -> ::#core::result::Result { 92 | #validator::validate(#param.as_ref())?; 93 | ::#core::result::Result::Ok(#create) 94 | } 95 | 96 | #[doc = #doc_comment_unsafe] 97 | #[allow(unsafe_code)] 98 | #[inline] 99 | #vis const unsafe fn new_unchecked(#param: #field_ty) -> Self { 100 | #create 101 | } 102 | 103 | #[inline] 104 | #[doc = #static_doc_comment] 105 | #[doc = ""] 106 | #[doc = "# Panics"] 107 | #[doc = ""] 108 | #[doc = "This function will panic if the provided raw string is not valid."] 109 | #[track_caller] 110 | pub fn from_static(raw: &'static str) -> Self { 111 | ::#alloc::borrow::ToOwned::to_owned(#ref_ty::from_static(raw)) 112 | } 113 | } 114 | } 115 | 116 | fn normalized_constructor(&self, normalizer: &syn::Type) -> proc_macro2::TokenStream { 117 | let normalizer_tokens = normalizer.to_token_stream(); 118 | let doc_comment = format!( 119 | "Constructs a new {} if it conforms to [`{}`] and normalizes the input", 120 | self.ty, normalizer_tokens 121 | ); 122 | 123 | let static_doc_comment = format!( 124 | "Constructs a new {} from a static reference if it conforms to [`{}`], normalizing \ 125 | the input", 126 | self.ty, normalizer_tokens 127 | ); 128 | 129 | let doc_comment_unsafe = format!( 130 | "Constructs a new {} without validation or normalization\n\n# Safety\n\nConsumers of \ 131 | this function must ensure that values conform to [`{}`] and are in normalized form. \ 132 | Failure to maintain this invariant may lead to undefined behavior.", 133 | self.ty, normalizer_tokens 134 | ); 135 | 136 | let ty = self.ty; 137 | let validator = crate::as_validator(normalizer); 138 | let normalizer = crate::as_normalizer(normalizer); 139 | let param = self.field.name.input_name(); 140 | let create = self.field.self_constructor(); 141 | let ref_ty = self.ref_ty; 142 | let field_ty = &self.field.ty; 143 | let core = self.std_lib.core(); 144 | 145 | let vis = self 146 | .expose_inner 147 | .then(|| proc_macro2::Ident::new("pub", proc_macro2::Span::call_site())); 148 | 149 | quote! { 150 | #[doc = #doc_comment] 151 | #[inline] 152 | #vis fn new(#param: #field_ty) -> ::#core::result::Result { 153 | let #param = ::#core::convert::From::from(#normalizer::normalize(#param.as_ref())?); 154 | ::#core::result::Result::Ok(#create) 155 | } 156 | 157 | #[doc = #doc_comment_unsafe] 158 | #[allow(unsafe_code)] 159 | #[inline] 160 | #vis const unsafe fn new_unchecked(#param: #field_ty) -> Self { 161 | #create 162 | } 163 | 164 | #[inline] 165 | #[doc = #static_doc_comment] 166 | #[doc = ""] 167 | #[doc = "# Panics"] 168 | #[doc = ""] 169 | #[doc = "This function will panic if the provided raw string is not valid."] 170 | #[track_caller] 171 | pub fn from_static(raw: &'static str) -> Self { 172 | #ref_ty::from_str(raw).expect(concat!("invalid ", stringify!(#ty))).into_owned() 173 | } 174 | } 175 | } 176 | 177 | fn make_into_boxed_ref(&self) -> proc_macro2::TokenStream { 178 | let doc = format!( 179 | "Converts this `{}` into a [`Box<{}>`]\n\nThis will drop any excess capacity.", 180 | self.ty, 181 | self.ref_ty.to_token_stream(), 182 | ); 183 | 184 | let ref_type = self.ref_ty; 185 | let field = &self.field.name; 186 | let alloc = self.std_lib.alloc(); 187 | let box_pointer_reinterpret_safety_comment = { 188 | let doc = format!( 189 | "SAFETY: `{ty}` is `#[repr(transparent)]` around a single `str` field, so a `*mut \ 190 | str` can be safely reinterpreted as a `*mut {ty}`", 191 | ty = self.ref_ty.to_token_stream(), 192 | ); 193 | 194 | quote! { 195 | #[doc = #doc] 196 | fn ptr_safety_comment() {} 197 | } 198 | }; 199 | 200 | quote! { 201 | #[doc = #doc] 202 | #[allow(unsafe_code)] 203 | #[inline] 204 | pub fn into_boxed_ref(self) -> ::#alloc::boxed::Box<#ref_type> { 205 | #box_pointer_reinterpret_safety_comment 206 | let box_str = ::#alloc::string::String::from(self.#field).into_boxed_str(); 207 | unsafe { ::#alloc::boxed::Box::from_raw(::#alloc::boxed::Box::into_raw(box_str) as *mut #ref_type) } 208 | } 209 | } 210 | } 211 | 212 | fn make_take(&self) -> proc_macro2::TokenStream { 213 | let field = &self.field.name; 214 | let field_ty = &self.field.ty; 215 | let doc = format!( 216 | "Unwraps the underlying [`{}`] value", 217 | field_ty.to_token_stream() 218 | ); 219 | 220 | let vis = self 221 | .expose_inner 222 | .then(|| proc_macro2::Ident::new("pub", proc_macro2::Span::call_site())); 223 | 224 | quote! { 225 | #[doc = #doc] 226 | #[inline] 227 | #vis fn take(self) -> #field_ty { 228 | self.#field 229 | } 230 | } 231 | } 232 | 233 | fn inherent(&self) -> proc_macro2::TokenStream { 234 | let name = self.ty; 235 | let constructor = self.constructor(); 236 | let into_boxed_ref = self.make_into_boxed_ref(); 237 | let into_string = self.make_take(); 238 | 239 | quote! { 240 | #[automatically_derived] 241 | impl #name { 242 | #constructor 243 | #into_boxed_ref 244 | #into_string 245 | } 246 | } 247 | } 248 | 249 | fn common_conversion(&self) -> proc_macro2::TokenStream { 250 | let ty = self.ty; 251 | let field_name = &self.field.name; 252 | let ref_ty = self.ref_ty; 253 | let core = self.std_lib.core(); 254 | let alloc = self.std_lib.alloc(); 255 | 256 | quote! { 257 | #[automatically_derived] 258 | impl ::#core::convert::From<&'_ #ref_ty> for #ty { 259 | #[inline] 260 | fn from(s: &#ref_ty) -> Self { 261 | ::#alloc::borrow::ToOwned::to_owned(s) 262 | } 263 | } 264 | 265 | #[automatically_derived] 266 | impl ::#core::convert::From<#ty> for ::#alloc::string::String { 267 | #[inline] 268 | fn from(s: #ty) -> Self { 269 | ::#core::convert::From::from(s.#field_name) 270 | } 271 | } 272 | 273 | #[automatically_derived] 274 | impl ::#core::borrow::Borrow<#ref_ty> for #ty { 275 | #[inline] 276 | fn borrow(&self) -> &#ref_ty { 277 | ::#core::ops::Deref::deref(self) 278 | } 279 | } 280 | 281 | #[automatically_derived] 282 | impl ::#core::convert::AsRef<#ref_ty> for #ty { 283 | #[inline] 284 | fn as_ref(&self) -> &#ref_ty { 285 | ::#core::ops::Deref::deref(self) 286 | } 287 | } 288 | 289 | #[automatically_derived] 290 | impl ::#core::convert::AsRef for #ty { 291 | #[inline] 292 | fn as_ref(&self) -> &str { 293 | self.as_str() 294 | } 295 | } 296 | 297 | 298 | #[automatically_derived] 299 | impl ::#core::convert::From<#ty> for ::#alloc::boxed::Box<#ref_ty> { 300 | #[inline] 301 | fn from(r: #ty) -> Self { 302 | r.into_boxed_ref() 303 | } 304 | } 305 | 306 | #[automatically_derived] 307 | impl ::#core::convert::From<::#alloc::boxed::Box<#ref_ty>> for #ty { 308 | #[inline] 309 | fn from(r: ::#alloc::boxed::Box<#ref_ty>) -> Self { 310 | r.into_owned() 311 | } 312 | } 313 | 314 | #[automatically_derived] 315 | impl<'a> ::#core::convert::From<::#alloc::borrow::Cow<'a, #ref_ty>> for #ty { 316 | #[inline] 317 | fn from(r: ::#alloc::borrow::Cow<'a, #ref_ty>) -> Self { 318 | match r { 319 | ::#alloc::borrow::Cow::Borrowed(b) => ::#alloc::borrow::ToOwned::to_owned(b), 320 | ::#alloc::borrow::Cow::Owned(o) => o, 321 | } 322 | } 323 | } 324 | 325 | #[automatically_derived] 326 | impl<'a> ::#core::convert::From<#ty> for ::#alloc::borrow::Cow<'a, #ref_ty> { 327 | #[inline] 328 | fn from(owned: #ty) -> Self { 329 | ::#alloc::borrow::Cow::Owned(owned) 330 | } 331 | } 332 | } 333 | } 334 | 335 | fn infallible_conversion(&self) -> proc_macro2::TokenStream { 336 | let ty = self.ty; 337 | let ref_ty = self.ref_ty; 338 | let field_name = &self.field.name; 339 | let core = self.std_lib.core(); 340 | let alloc = self.std_lib.alloc(); 341 | 342 | quote! { 343 | #[automatically_derived] 344 | impl ::#core::convert::From<::#alloc::string::String> for #ty { 345 | #[inline] 346 | fn from(s: ::#alloc::string::String) -> Self { 347 | Self::new(From::from(s)) 348 | } 349 | } 350 | 351 | #[automatically_derived] 352 | impl ::#core::convert::From<&'_ str> for #ty { 353 | #[inline] 354 | fn from(s: &str) -> Self { 355 | Self::new(::#core::convert::From::from(s)) 356 | } 357 | } 358 | 359 | #[automatically_derived] 360 | impl ::#core::convert::From<::#alloc::boxed::Box> for #ty { 361 | #[inline] 362 | fn from(s: ::#alloc::boxed::Box) -> Self { 363 | Self::new(::#core::convert::From::from(s)) 364 | } 365 | } 366 | 367 | #[automatically_derived] 368 | impl ::#core::str::FromStr for #ty { 369 | type Err = ::#core::convert::Infallible; 370 | 371 | #[inline] 372 | fn from_str(s: &str) -> ::#core::result::Result { 373 | ::#core::result::Result::Ok(::#core::convert::From::from(s)) 374 | } 375 | } 376 | 377 | #[automatically_derived] 378 | impl ::#core::borrow::Borrow for #ty { 379 | #[inline] 380 | fn borrow(&self) -> &str { 381 | self.as_str() 382 | } 383 | } 384 | 385 | #[automatically_derived] 386 | impl ::#core::ops::Deref for #ty { 387 | type Target = #ref_ty; 388 | 389 | #[inline] 390 | fn deref(&self) -> &Self::Target { 391 | #ref_ty::from_str(::#core::convert::AsRef::as_ref(&self.#field_name)) 392 | } 393 | } 394 | } 395 | } 396 | 397 | fn unchecked_safety_comment(is_normalized: bool) -> proc_macro2::TokenStream { 398 | let doc = format!( 399 | "SAFETY: The value was satisfies the type's invariant and conforms to the required \ 400 | implicit contracts of the {}.", 401 | if is_normalized { 402 | "normalizer" 403 | } else { 404 | "validator" 405 | }, 406 | ); 407 | 408 | quote! { 409 | #[doc = #doc] 410 | fn unchecked_safety_comment() {} 411 | } 412 | } 413 | 414 | fn fallible_conversion(&self, validator: &syn::Type) -> proc_macro2::TokenStream { 415 | let ty = self.ty; 416 | let ref_ty = self.ref_ty; 417 | let field_name = &self.field.name; 418 | let field_ty = &self.field.ty; 419 | let validator = crate::as_validator(validator); 420 | let core = self.std_lib.core(); 421 | let alloc = self.std_lib.alloc(); 422 | let unchecked_safety_comment = Self::unchecked_safety_comment(false); 423 | 424 | quote! { 425 | #[automatically_derived] 426 | impl ::#core::convert::TryFrom<::#alloc::string::String> for #ty { 427 | type Error = #validator::Error; 428 | 429 | #[inline] 430 | fn try_from(s: ::#alloc::string::String) -> ::#core::result::Result { 431 | const fn ensure_try_from_string_error_converts_to_validator_error>::Error>>() {} 432 | ensure_try_from_string_error_converts_to_validator_error::(); 433 | 434 | Self::new(::#core::convert::TryFrom::try_from(s)?) 435 | } 436 | } 437 | 438 | #[automatically_derived] 439 | impl ::#core::convert::TryFrom<&'_ str> for #ty { 440 | type Error = #validator::Error; 441 | 442 | #[inline] 443 | fn try_from(s: &str) -> ::#core::result::Result { 444 | let ref_ty = #ref_ty::from_str(s)?; 445 | ::#core::result::Result::Ok(::#alloc::borrow::ToOwned::to_owned(ref_ty)) 446 | } 447 | } 448 | 449 | #[automatically_derived] 450 | impl ::#core::str::FromStr for #ty { 451 | type Err = #validator::Error; 452 | 453 | #[inline] 454 | fn from_str(s: &str) -> ::#core::result::Result { 455 | let ref_ty = #ref_ty::from_str(s)?; 456 | ::#core::result::Result::Ok(::#alloc::borrow::ToOwned::to_owned(ref_ty)) 457 | } 458 | } 459 | 460 | #[automatically_derived] 461 | impl ::#core::borrow::Borrow for #ty { 462 | #[inline] 463 | fn borrow(&self) -> &str { 464 | self.as_str() 465 | } 466 | } 467 | 468 | #[automatically_derived] 469 | impl ::#core::ops::Deref for #ty { 470 | type Target = #ref_ty; 471 | 472 | #[allow(unsafe_code)] 473 | #[inline] 474 | fn deref(&self) -> &Self::Target { 475 | #unchecked_safety_comment 476 | unsafe { #ref_ty::from_str_unchecked(::#core::convert::AsRef::as_ref(&self.#field_name)) } 477 | } 478 | } 479 | } 480 | } 481 | 482 | fn normalized_conversion(&self, normalizer: &syn::Type) -> proc_macro2::TokenStream { 483 | let ty = self.ty; 484 | let ref_ty = self.ref_ty; 485 | let field_name = &self.field.name; 486 | let field_ty = &self.field.ty; 487 | let validator = crate::as_validator(normalizer); 488 | let core = self.std_lib.core(); 489 | let alloc = self.std_lib.alloc(); 490 | let unchecked_safety_comment = Self::unchecked_safety_comment(true); 491 | 492 | quote! { 493 | #[automatically_derived] 494 | impl ::#core::convert::TryFrom<::#alloc::string::String> for #ty { 495 | type Error = #validator::Error; 496 | 497 | #[inline] 498 | fn try_from(s: ::#alloc::string::String) -> ::#core::result::Result { 499 | const fn ensure_try_from_string_error_converts_to_validator_error>::Error>>() {} 500 | ensure_try_from_string_error_converts_to_validator_error::(); 501 | 502 | Self::new(::#core::convert::TryFrom::try_from(s)?) 503 | } 504 | } 505 | 506 | #[automatically_derived] 507 | impl ::#core::convert::TryFrom<&'_ str> for #ty { 508 | type Error = #validator::Error; 509 | 510 | #[inline] 511 | fn try_from(s: &str) -> ::#core::result::Result { 512 | let ref_ty = #ref_ty::from_str(s)?; 513 | ::#core::result::Result::Ok(ref_ty.into_owned()) 514 | } 515 | } 516 | 517 | #[automatically_derived] 518 | impl ::#core::str::FromStr for #ty { 519 | type Err = #validator::Error; 520 | 521 | #[inline] 522 | fn from_str(s: &str) -> ::#core::result::Result { 523 | let ref_ty = #ref_ty::from_str(s)?; 524 | ::#core::result::Result::Ok(ref_ty.into_owned()) 525 | } 526 | } 527 | 528 | #[automatically_derived] 529 | impl ::#core::ops::Deref for #ty { 530 | type Target = #ref_ty; 531 | 532 | #[allow(unsafe_code)] 533 | #[inline] 534 | fn deref(&self) -> &Self::Target { 535 | #unchecked_safety_comment 536 | unsafe { #ref_ty::from_str_unchecked(&self.#field_name) } 537 | } 538 | } 539 | } 540 | } 541 | 542 | fn conversion(&self) -> proc_macro2::TokenStream { 543 | let common = self.common_conversion(); 544 | let convert = match &self.check_mode { 545 | CheckMode::None => self.infallible_conversion(), 546 | CheckMode::Validate(validator) => self.fallible_conversion(validator), 547 | CheckMode::Normalize(normalizer) => self.normalized_conversion(normalizer), 548 | }; 549 | 550 | quote! { 551 | #common 552 | #convert 553 | } 554 | } 555 | 556 | pub fn tokens(&self) -> proc_macro2::TokenStream { 557 | let clone = self.impls.clone.to_owned_impl(self); 558 | let display = self.impls.display.to_owned_impl(self); 559 | let debug = self.impls.debug.to_owned_impl(self); 560 | let ord = self.impls.ord.to_owned_impl(self); 561 | let serde = self.impls.serde.to_owned_impl(self); 562 | 563 | let owned_attrs: proc_macro2::TokenStream = 564 | self.attrs.iter().map(|a| quote! {#[#a]}).collect(); 565 | let body = &self.body; 566 | let inherent = self.inherent(); 567 | let conversion = self.conversion(); 568 | 569 | quote! { 570 | #clone 571 | #[derive(Hash, PartialEq, Eq)] 572 | #[repr(transparent)] 573 | #owned_attrs 574 | #body 575 | 576 | #inherent 577 | #conversion 578 | #debug 579 | #display 580 | #ord 581 | #serde 582 | } 583 | } 584 | } 585 | -------------------------------------------------------------------------------- /aliri_braid_impl/src/codegen/borrowed.rs: -------------------------------------------------------------------------------- 1 | use quote::{quote, ToTokens, TokenStreamExt}; 2 | 3 | use super::{impls::ToImpl, AttrList, CheckMode, Field, FieldName, Impls, StdLib}; 4 | 5 | pub struct RefCodeGen<'a> { 6 | pub doc: &'a [syn::Lit], 7 | pub common_attrs: &'a [syn::Attribute], 8 | pub attrs: &'a AttrList, 9 | pub vis: &'a syn::Visibility, 10 | pub ty: &'a syn::Type, 11 | pub ident: syn::Ident, 12 | pub field: Field, 13 | pub check_mode: &'a CheckMode, 14 | pub owned_ty: Option<&'a syn::Ident>, 15 | pub std_lib: &'a StdLib, 16 | pub impls: &'a Impls, 17 | } 18 | 19 | impl<'a> RefCodeGen<'a> { 20 | fn inherent(&self) -> proc_macro2::TokenStream { 21 | let ty = &self.ty; 22 | let field_name = &self.field.name; 23 | let inherent = self.check_inherent(); 24 | 25 | quote! { 26 | #[automatically_derived] 27 | impl #ty { 28 | #inherent 29 | 30 | /// Provides access to the underlying value as a string slice. 31 | #[inline] 32 | pub const fn as_str(&self) -> &str { 33 | &self.#field_name 34 | } 35 | } 36 | } 37 | } 38 | 39 | fn check_inherent(&self) -> proc_macro2::TokenStream { 40 | match self.check_mode { 41 | CheckMode::None => self.infallible_inherent(), 42 | CheckMode::Validate(validator) => self.fallible_inherent(validator), 43 | CheckMode::Normalize(normalizer) => self.normalized_inherent(normalizer), 44 | } 45 | } 46 | 47 | fn pointer_reinterpret_safety_comment(&self, is_mut: bool) -> proc_macro2::TokenStream { 48 | let doc = format!( 49 | "SAFETY: `{ty}` is `#[repr(transparent)]` around a single `str` field, so a `*{ptr} \ 50 | str` can be safely reinterpreted as a `*{ptr} {ty}`", 51 | ty = self.ident, 52 | ptr = if is_mut { "mut" } else { "const" }, 53 | ); 54 | 55 | quote! { 56 | #[doc = #doc] 57 | fn ptr_safety_comment() {} 58 | } 59 | } 60 | 61 | fn unchecked_safety_comment(is_normalized: bool) -> proc_macro2::TokenStream { 62 | let doc = format!( 63 | "SAFETY: The value was just checked and found to already conform to the required \ 64 | implicit contracts of the {}.", 65 | if is_normalized { 66 | "normalizer" 67 | } else { 68 | "validator" 69 | }, 70 | ); 71 | 72 | quote! { 73 | #[doc = #doc] 74 | fn unchecked_safety_comment() {} 75 | } 76 | } 77 | 78 | fn infallible_inherent(&self) -> proc_macro2::TokenStream { 79 | let ty = &self.ty; 80 | let core = self.std_lib.core(); 81 | let alloc = self.std_lib.alloc(); 82 | 83 | let doc_comment = format!( 84 | "Transparently reinterprets the string slice as a strongly-typed {}", 85 | self.ident 86 | ); 87 | 88 | let static_doc_comment = format!( 89 | "Transparently reinterprets the static string slice as a strongly-typed {}", 90 | self.ident 91 | ); 92 | 93 | let pointer_reinterpret_safety_comment = self.pointer_reinterpret_safety_comment(false); 94 | 95 | let into_owned = self.owned_ty.map(|owned_ty| { 96 | let into_owned_doc = format!( 97 | "Converts a [`Box<{}>`] into a [`{}`] without copying or allocating", 98 | self.ident, owned_ty, 99 | ); 100 | 101 | let box_pointer_reinterpret_safety_comment = 102 | self.pointer_reinterpret_safety_comment(true); 103 | 104 | quote! { 105 | #[allow(unsafe_code)] 106 | #[inline] 107 | #[doc = #into_owned_doc] 108 | pub fn into_owned(self: ::#alloc::boxed::Box<#ty>) -> #owned_ty { 109 | #box_pointer_reinterpret_safety_comment 110 | let raw = ::#alloc::boxed::Box::into_raw(self); 111 | let boxed = unsafe { ::#alloc::boxed::Box::from_raw(raw as *mut str) }; 112 | #owned_ty::new(::#core::convert::From::from(boxed)) 113 | } 114 | } 115 | }); 116 | 117 | quote! { 118 | #[allow(unsafe_code)] 119 | #[inline] 120 | #[doc = #doc_comment] 121 | pub const fn from_str(raw: &str) -> &Self { 122 | let ptr: *const str = raw; 123 | #pointer_reinterpret_safety_comment 124 | unsafe { 125 | &*(ptr as *const Self) 126 | } 127 | } 128 | 129 | #[inline] 130 | #[doc = #static_doc_comment] 131 | #[track_caller] 132 | pub const fn from_static(raw: &'static str) -> &'static Self { 133 | Self::from_str(raw) 134 | } 135 | 136 | #into_owned 137 | } 138 | } 139 | 140 | fn fallible_inherent(&self, validator: &syn::Type) -> proc_macro2::TokenStream { 141 | let doc_comment = format!( 142 | "Transparently reinterprets the string slice as a strongly-typed {} if it conforms to \ 143 | [`{}`]", 144 | self.ident, 145 | validator.to_token_stream(), 146 | ); 147 | 148 | let static_doc_comment = format!( 149 | "Transparently reinterprets the static string slice as a strongly-typed {} if it \ 150 | conforms to [`{}`]", 151 | self.ident, 152 | validator.to_token_stream(), 153 | ); 154 | 155 | let doc_comment_unsafe = format!( 156 | "Transparently reinterprets the string slice as a strongly-typed {} without validating", 157 | self.ident, 158 | ); 159 | 160 | let ty = &self.ty; 161 | let core = self.std_lib.core(); 162 | let alloc = self.std_lib.alloc(); 163 | let unchecked_safety_comment = Self::unchecked_safety_comment(false); 164 | let pointer_reinterpret_safety_comment = self.pointer_reinterpret_safety_comment(false); 165 | let into_owned = self.owned_ty.map(|owned_ty| { 166 | let into_owned_doc = format!( 167 | "Converts a [`Box<{}>`] into a [`{}`] without copying or allocating", 168 | self.ident, owned_ty, 169 | ); 170 | 171 | let box_pointer_reinterpret_safety_comment = 172 | self.pointer_reinterpret_safety_comment(true); 173 | 174 | quote! { 175 | #[allow(unsafe_code)] 176 | #[inline] 177 | #[doc = #into_owned_doc] 178 | pub fn into_owned(self: ::#alloc::boxed::Box<#ty>) -> #owned_ty { 179 | #box_pointer_reinterpret_safety_comment 180 | let raw = ::#alloc::boxed::Box::into_raw(self); 181 | let boxed = unsafe { ::#alloc::boxed::Box::from_raw(raw as *mut str) }; 182 | let s = ::#core::convert::From::from(boxed); 183 | #unchecked_safety_comment 184 | unsafe { #owned_ty::new_unchecked(s) } 185 | } 186 | } 187 | }); 188 | 189 | let validator = crate::as_validator(validator); 190 | 191 | quote! { 192 | #[allow(unsafe_code)] 193 | #[inline] 194 | #[doc = #doc_comment] 195 | pub fn from_str(raw: &str) -> ::#core::result::Result<&Self, #validator::Error> { 196 | #validator::validate(raw)?; 197 | #unchecked_safety_comment 198 | ::#core::result::Result::Ok(unsafe { Self::from_str_unchecked(raw) }) 199 | } 200 | 201 | #[allow(unsafe_code)] 202 | #[inline] 203 | #[doc = #doc_comment_unsafe] 204 | pub const unsafe fn from_str_unchecked(raw: &str) -> &Self { 205 | #pointer_reinterpret_safety_comment 206 | &*(raw as *const str as *const Self) 207 | } 208 | 209 | #[inline] 210 | #[doc = #static_doc_comment] 211 | #[doc = ""] 212 | #[doc = "# Panics"] 213 | #[doc = ""] 214 | #[doc = "This function will panic if the provided raw string is not valid."] 215 | #[track_caller] 216 | pub fn from_static(raw: &'static str) -> &'static Self { 217 | Self::from_str(raw).expect(concat!("invalid ", stringify!(#ty))) 218 | } 219 | 220 | #into_owned 221 | } 222 | } 223 | 224 | fn normalized_inherent(&self, normalizer: &syn::Type) -> proc_macro2::TokenStream { 225 | let doc_comment = format!( 226 | "Transparently reinterprets the string slice as a strongly-typed {} if it conforms to \ 227 | [`{}`], normalizing if necessary", 228 | self.ident, 229 | normalizer.to_token_stream(), 230 | ); 231 | 232 | let static_doc_comment = format!( 233 | "Transparently reinterprets a static string slice as a strongly-typed {} if it \ 234 | conforms to [`{}`], normalizing if necessary", 235 | self.ident, 236 | normalizer.to_token_stream(), 237 | ); 238 | 239 | let doc_comment_norm = format!( 240 | "Transparently reinterprets the string slice as a strongly-typed `{}` if it conforms \ 241 | to [`{}`], producing an error if normalization is necessary", 242 | self.ident, 243 | normalizer.to_token_stream(), 244 | ); 245 | 246 | let doc_comment_unsafe = format!( 247 | "Transparently reinterprets the string slice as a strongly-typed `{}` without \ 248 | validating\n\n# Safety\n\nCalls to this function must ensure that the value being \ 249 | passed conforms to [`{}`] and is already in normalized form. Failure to do this may \ 250 | result in undefined behavior if other code relies on this invariant.", 251 | self.ident, 252 | normalizer.to_token_stream(), 253 | ); 254 | 255 | let doc_comment_cow_unsafe = format!( 256 | "Transparently reinterprets the [`Cow`][std::borrow::Cow] as a strongly-typed \ 257 | [`Cow`][std::borrow::Cow]`<{}>` without validating\n\n# Safety\n\nCalls to this \ 258 | function must ensure that the value being passed conforms to [`{}`] and is already \ 259 | in normalized form. Failure to do this may result in undefined behavior if other \ 260 | code relies on this invariant.", 261 | self.ident, 262 | normalizer.to_token_stream(), 263 | ); 264 | 265 | let ty = &self.ty; 266 | let core = self.std_lib.core(); 267 | let alloc = self.std_lib.alloc(); 268 | let unchecked_safety_comment = Self::unchecked_safety_comment(true); 269 | let pointer_reinterpret_safety_comment = self.pointer_reinterpret_safety_comment(false); 270 | 271 | let validator = crate::as_validator(normalizer); 272 | let normalizer = crate::as_normalizer(normalizer); 273 | 274 | let into_owned = self.owned_ty.map(|owned_ty| { 275 | let into_owned_doc = format!( 276 | "Converts a [`Box<{}>`] into a [`{}`] without copying or allocating", 277 | self.ident, 278 | owned_ty, 279 | ); 280 | 281 | let box_pointer_reinterpret_safety_comment = self.pointer_reinterpret_safety_comment(true); 282 | 283 | quote! { 284 | #[allow(unsafe_code)] 285 | #[inline] 286 | #[doc = #doc_comment] 287 | pub fn from_str(raw: &str) -> ::#core::result::Result<::#alloc::borrow::Cow, #validator::Error> { 288 | let cow = #normalizer::normalize(raw)?; 289 | #unchecked_safety_comment 290 | ::#core::result::Result::Ok(unsafe { Self::from_cow_str_unchecked(cow) }) 291 | } 292 | 293 | #[allow(unsafe_code)] 294 | #[inline] 295 | #[doc = #doc_comment_cow_unsafe] 296 | unsafe fn from_cow_str_unchecked(cow: ::#alloc::borrow::Cow) -> ::#alloc::borrow::Cow { 297 | match cow { 298 | ::#alloc::borrow::Cow::Borrowed(raw) => { 299 | let value = Self::from_str_unchecked(raw); 300 | ::#alloc::borrow::Cow::Borrowed(value) 301 | } 302 | ::#alloc::borrow::Cow::Owned(normalized) => { 303 | let value = #owned_ty::new_unchecked(::#core::convert::From::from(normalized)); 304 | ::#alloc::borrow::Cow::Owned(value) 305 | } 306 | } 307 | } 308 | 309 | #[allow(unsafe_code)] 310 | #[inline] 311 | #[doc = #into_owned_doc] 312 | pub fn into_owned(self: ::#alloc::boxed::Box<#ty>) -> #owned_ty { 313 | #box_pointer_reinterpret_safety_comment 314 | let raw = ::#alloc::boxed::Box::into_raw(self); 315 | let boxed = unsafe { ::#alloc::boxed::Box::from_raw(raw as *mut str) }; 316 | let s = ::#core::convert::From::from(boxed); 317 | #unchecked_safety_comment 318 | unsafe { #owned_ty::new_unchecked(s) } 319 | } 320 | } 321 | }); 322 | 323 | quote! { 324 | #[allow(unsafe_code)] 325 | #[inline] 326 | #[doc = #doc_comment_norm] 327 | pub fn from_normalized_str(raw: &str) -> ::#core::result::Result<&Self, #validator::Error> { 328 | #validator::validate(raw)?; 329 | #unchecked_safety_comment 330 | ::#core::result::Result::Ok(unsafe { Self::from_str_unchecked(raw) }) 331 | } 332 | 333 | #[allow(unsafe_code)] 334 | #[inline] 335 | #[doc = #doc_comment_unsafe] 336 | pub const unsafe fn from_str_unchecked(raw: &str) -> &Self { 337 | #pointer_reinterpret_safety_comment 338 | &*(raw as *const str as *const Self) 339 | } 340 | 341 | #[inline] 342 | #[doc = #static_doc_comment] 343 | #[doc = ""] 344 | #[doc = "# Panics"] 345 | #[doc = ""] 346 | #[doc = "This function will panic if the provided raw string is not normalized."] 347 | #[track_caller] 348 | pub fn from_static(raw: &'static str) -> &'static Self { 349 | Self::from_normalized_str(raw).expect(concat!("non-normalized ", stringify!(#ty))) 350 | } 351 | 352 | #into_owned 353 | } 354 | } 355 | 356 | fn comparison(&self) -> Option { 357 | self.owned_ty.map(|owned_ty| { 358 | let ty = &self.ty; 359 | let core = self.std_lib.core(); 360 | let alloc = self.std_lib.alloc(); 361 | 362 | let create = match &self.field.name { 363 | FieldName::Unnamed => quote! { #owned_ty(self.0.into()) }, 364 | FieldName::Named(field_name) => { 365 | quote! { #owned_ty { #field_name: self.#field_name.into() } } 366 | } 367 | }; 368 | 369 | quote! { 370 | #[automatically_derived] 371 | impl ::#alloc::borrow::ToOwned for #ty { 372 | type Owned = #owned_ty; 373 | 374 | #[inline] 375 | fn to_owned(&self) -> Self::Owned { 376 | #create 377 | } 378 | } 379 | 380 | #[automatically_derived] 381 | impl ::#core::cmp::PartialEq<#ty> for #owned_ty { 382 | #[inline] 383 | fn eq(&self, other: &#ty) -> bool { 384 | self.as_str() == other.as_str() 385 | } 386 | } 387 | 388 | #[automatically_derived] 389 | impl ::#core::cmp::PartialEq<#owned_ty> for #ty { 390 | #[inline] 391 | fn eq(&self, other: &#owned_ty) -> bool { 392 | self.as_str() == other.as_str() 393 | } 394 | } 395 | 396 | #[automatically_derived] 397 | impl ::#core::cmp::PartialEq<&'_ #ty> for #owned_ty { 398 | #[inline] 399 | fn eq(&self, other: &&#ty) -> bool { 400 | self.as_str() == other.as_str() 401 | } 402 | } 403 | 404 | #[automatically_derived] 405 | impl ::#core::cmp::PartialEq<#owned_ty> for &'_ #ty { 406 | #[inline] 407 | fn eq(&self, other: &#owned_ty) -> bool { 408 | self.as_str() == other.as_str() 409 | } 410 | } 411 | } 412 | }) 413 | } 414 | 415 | fn conversion(&self) -> proc_macro2::TokenStream { 416 | let ty = &self.ty; 417 | let field_name = &self.field.name; 418 | let core = self.std_lib.core(); 419 | let alloc = self.std_lib.alloc(); 420 | let pointer_reinterpret_safety_comment = self.pointer_reinterpret_safety_comment(false); 421 | 422 | let from_str = match &self.check_mode { 423 | CheckMode::None => quote! { 424 | #[automatically_derived] 425 | impl<'a> ::#core::convert::From<&'a str> for &'a #ty { 426 | #[inline] 427 | fn from(s: &'a str) -> &'a #ty { 428 | #ty::from_str(s) 429 | } 430 | } 431 | 432 | #[automatically_derived] 433 | impl ::#core::borrow::Borrow for #ty { 434 | #[inline] 435 | fn borrow(&self) -> &str { 436 | &self.#field_name 437 | } 438 | } 439 | }, 440 | CheckMode::Validate(validator) => { 441 | let validator = crate::as_validator(validator); 442 | quote! { 443 | #[automatically_derived] 444 | impl<'a> ::#core::convert::TryFrom<&'a str> for &'a #ty { 445 | type Error = #validator::Error; 446 | 447 | #[inline] 448 | fn try_from(s: &'a str) -> ::#core::result::Result<&'a #ty, Self::Error> { 449 | #ty::from_str(s) 450 | } 451 | } 452 | 453 | #[automatically_derived] 454 | impl ::#core::borrow::Borrow for #ty { 455 | #[inline] 456 | fn borrow(&self) -> &str { 457 | &self.#field_name 458 | } 459 | } 460 | } 461 | } 462 | CheckMode::Normalize(normalizer) => { 463 | let validator = crate::as_validator(normalizer); 464 | quote! { 465 | #[automatically_derived] 466 | impl<'a> ::#core::convert::TryFrom<&'a str> for &'a #ty { 467 | type Error = #validator::Error; 468 | 469 | #[inline] 470 | fn try_from(s: &'a str) -> ::#core::result::Result<&'a #ty, Self::Error> { 471 | #ty::from_normalized_str(s) 472 | } 473 | } 474 | } 475 | } 476 | }; 477 | 478 | let alloc_from = self.owned_ty.is_some().then(|| { 479 | quote!{ 480 | #[automatically_derived] 481 | impl<'a> ::#core::convert::From<&'a #ty> for ::#alloc::borrow::Cow<'a, #ty> { 482 | #[inline] 483 | fn from(r: &'a #ty) -> Self { 484 | ::#alloc::borrow::Cow::Borrowed(r) 485 | } 486 | } 487 | 488 | 489 | #[automatically_derived] 490 | impl<'a, 'b: 'a> ::#core::convert::From<&'a ::#alloc::borrow::Cow<'b, #ty>> for &'a #ty { 491 | #[inline] 492 | fn from(r: &'a ::#alloc::borrow::Cow<'b, #ty>) -> &'a #ty { 493 | ::#core::borrow::Borrow::borrow(r) 494 | } 495 | } 496 | 497 | #[automatically_derived] 498 | impl ::#core::convert::From<&'_ #ty> for ::#alloc::rc::Rc<#ty> { 499 | #[allow(unsafe_code)] 500 | #[inline] 501 | fn from(r: &'_ #ty) -> Self { 502 | #pointer_reinterpret_safety_comment 503 | let rc = ::#alloc::rc::Rc::::from(r.as_str()); 504 | unsafe { ::#alloc::rc::Rc::from_raw(::#alloc::rc::Rc::into_raw(rc) as *const #ty) } 505 | } 506 | } 507 | 508 | #[automatically_derived] 509 | impl ::#core::convert::From<&'_ #ty> for ::#alloc::sync::Arc<#ty> { 510 | #[allow(unsafe_code)] 511 | #[inline] 512 | fn from(r: &'_ #ty) -> Self { 513 | #pointer_reinterpret_safety_comment 514 | let arc = ::#alloc::sync::Arc::::from(r.as_str()); 515 | unsafe { ::#alloc::sync::Arc::from_raw(::#alloc::sync::Arc::into_raw(arc) as *const #ty) } 516 | } 517 | } 518 | } 519 | }); 520 | 521 | quote! { 522 | #from_str 523 | 524 | #[automatically_derived] 525 | impl ::#core::convert::AsRef for #ty { 526 | #[inline] 527 | fn as_ref(&self) -> &str { 528 | &self.#field_name 529 | } 530 | } 531 | 532 | #alloc_from 533 | } 534 | } 535 | 536 | pub fn tokens(&self) -> proc_macro2::TokenStream { 537 | let inherent = self.inherent(); 538 | let comparison = self.comparison(); 539 | let conversion = self.conversion(); 540 | let debug = self.impls.debug.to_borrowed_impl(self); 541 | let display = self.impls.display.to_borrowed_impl(self); 542 | let ord = self.impls.ord.to_borrowed_impl(self); 543 | let serde = self.impls.serde.to_borrowed_impl(self); 544 | 545 | let ref_doc: proc_macro2::TokenStream = 546 | self.doc.iter().map(|d| quote! { #[doc = #d] }).collect(); 547 | let ref_attrs: proc_macro2::TokenStream = 548 | self.attrs.iter().map(|a| quote! {#[#a]}).collect(); 549 | let common_attrs = { 550 | let mut attrs = proc_macro2::TokenStream::new(); 551 | if self.doc.is_empty() { 552 | attrs.append_all(self.common_attrs); 553 | } else { 554 | attrs.append_all(self.common_attrs.iter().filter(|a| !is_doc_attribute(a))); 555 | } 556 | attrs 557 | }; 558 | let vis = self.vis; 559 | let ty = &self.ty; 560 | let field_attrs = { 561 | let mut attrs = proc_macro2::TokenStream::new(); 562 | attrs.append_all(&self.field.attrs); 563 | attrs 564 | }; 565 | let body = match &self.field.name { 566 | FieldName::Named(name) => quote! ( { #field_attrs #name: str } ), 567 | FieldName::Unnamed => quote! { ( #field_attrs str ); }, 568 | }; 569 | 570 | quote! { 571 | #[repr(transparent)] 572 | #[derive(Hash, PartialEq, Eq)] 573 | #ord 574 | #ref_doc 575 | #ref_attrs 576 | #common_attrs 577 | #vis struct #ty #body 578 | 579 | #inherent 580 | #comparison 581 | #conversion 582 | #debug 583 | #display 584 | #serde 585 | } 586 | } 587 | } 588 | 589 | fn is_doc_attribute(attr: &syn::Attribute) -> bool { 590 | if let Some(ident) = attr.path().get_ident() { 591 | ident == "doc" 592 | } else { 593 | false 594 | } 595 | } 596 | --------------------------------------------------------------------------------