token-string 0.8.3

Short (up to 65,535 bytes) immutable strings to e.g. parse tokens, implemented in Rust. These are sometimes called 'German Strings', because Germans have written the paper mentioning them.
Documentation
// SPDX-FileCopyrightText: Copyright (C) 2024 Roland Csaszar
// SPDX-License-Identifier: MPL-2.0
//
// Project:  token-string
// File:     test_builder.rs
// Date:     24.Nov.2024
// =============================================================================
//! Test the concatenation of `TokenStrings`s.

#![expect(clippy::tests_outside_test_module, reason = "tests directory")]

mod errors {

	use assert2::{check, let_assert};
	use token_string::{Builder, MAX_LENGTH, TkStrError, TokenString};

	#[test]
	fn too_many() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let_assert!(Ok(s4) = TokenString::try_from("!"));
		let mut builder = Builder::<'_, 3>::new(&s1);
		let_assert!(Ok(_) = builder.concat_checked(&s2));
		let_assert!(Ok(_) = builder.concat_checked(&s3));
		let_assert!(Err(e) = builder.concat_checked(&s4));
		check!(e == TkStrError::TooMany(3));
	}

	#[test]
	fn too_big() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let big = vec![b'1'; MAX_LENGTH].into_boxed_slice();
		let_assert!(Ok(s4) = TokenString::try_from(&*big));
		let mut builder = Builder::<'_, 4>::new(&s1);
		let_assert!(Ok(_) = builder.concat_checked(&s2));
		let_assert!(Ok(_) = builder.concat_checked(&s3));
		let_assert!(Err(e) = builder.concat_checked(&s4));
		check!(e == TkStrError::TooBig(65547));
	}

	#[test]
	fn too_many_panic() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let_assert!(Ok(s4) = TokenString::try_from("!"));

		let_assert!(
			Err(panics) = std::panic::catch_unwind(|| {
				let mut builder = Builder::<'_, 3>::new(&s1);
				let_assert!(Ok(_) = builder.concat_checked(&s2));
				let_assert!(Ok(_) = builder.concat_checked(&s3));
				let _c = builder.concat_unchecked(&s4);
			})
		);
		let_assert!(Some(msg) = panics.downcast_ref::<&str>());
		check!(
			msg == &"more strings concatenated than reserved space in Builder"
		);
	}

	#[test]
	fn not_zero_panic() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));

		let_assert!(
			Err(panics) = std::panic::catch_unwind(|| {
				let mut _builder = Builder::<'_, 0>::new(&s1);
			})
		);
		let_assert!(Some(msg) = panics.downcast_ref::<&str>());
		check!(msg == &"the number of elements must not be 0");
	}
}

mod func {
	use assert2::{check, let_assert};
	use token_string::{Builder, Collect as _, Concat as _, TokenString};

	#[test]
	fn concat_collect() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let_assert!(Ok(s4) = TokenString::try_from("!"));

		let mut builder = Builder::<'_, 4>::new(&s1);
		let builder_2 = builder.concat(&s2).concat(&s3).concat(&s4);
		let_assert!(Ok(res) = builder_2.collect());
		check!(&res == "Hello, world!");
	}

	#[test]
	fn collect_unchecked_concat() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let_assert!(Ok(s4) = TokenString::try_from("!"));

		let mut builder = Builder::<'_, 4>::new(&s1);
		let builder_2 = builder
			.concat_unchecked(&s2)
			.concat_unchecked(&s3)
			.concat_unchecked(&s4);
		let_assert!(Ok(res) = builder_2.collect());
		check!(&res == "Hello, world!");
	}

	#[test]
	fn to_string() {
		let_assert!(Ok(s1) = TokenString::try_from("Hello"));
		let_assert!(Ok(s2) = TokenString::try_from(", "));
		let_assert!(Ok(s3) = TokenString::try_from("world"));
		let_assert!(Ok(s4) = TokenString::try_from("!"));

		let mut builder = Builder::<'_, 4>::new(&s1);
		let_assert!(
			Ok(builder_2) = builder.concat(&s2).concat(&s3).concat(&s4)
		);
		check!(
			builder_2.to_string()
				== "Builder < 'Hello' + ', ' + 'world' + '!' >"
		);
	}
}

mod properties {
	use assert2::let_assert;
	use proptest::prelude::*;
	use token_string::{Builder, TokenString};

	fn vec_of_string(len: usize) -> impl Strategy<Value = Vec<String>> {
		prop::collection::vec(".*", 1 .. len).prop_flat_map(Just)
	}

	proptest! {

	#[test]
	fn concat_2(a in ".*", b in ".*") {
		let_assert!(Ok(s1) = TokenString::try_from(&a));
		let_assert!(Ok(s2) = TokenString::try_from(&b));
		let mut builder = Builder::<'_, 2>::new(&s1);
		let_assert!(Ok(c) = builder.concat_checked(&s2));
		let_assert!(Ok(res) = c.collect_checked());
		let mut exp = a;
		exp.push_str(&b);
		prop_assert!(res == exp);
	}

	#[test]
	#[cfg_attr(miri, ignore)] // takes too long for miri - 10 hours!
	fn concat_many(v in vec_of_string(100)) {
		let strings: Vec<TokenString> = v.iter()
			.map(|s| {TokenString::from_str_unchecked(s)}).collect();

		let mut builder = Builder::<'_, 100>::new(&strings[0]);
		let mut exp = strings[0].as_string();
		for s in strings.iter().skip(1) {
			let_assert!(Ok(_c) = builder.concat_checked(s));
			exp.push_str(s.as_str());
		}
		let_assert!(Ok(res) = builder.collect_checked());
		prop_assert!(res == exp);
	}

	}
}