try_create/lib.rs
1#![deny(rustdoc::broken_intra_doc_links)]
2
3//! This crate provides a set of traits for standardizing object creation
4//! patterns in Rust, covering infallible, fallible, conditional, and
5//! policy-based validated construction.
6//!
7//! # Core Traits
8//!
9//! - [`IntoInner`]: A trait for types that can be converted into an "inner"
10//! value. This is often a prerequisite for construction traits.
11//! - [`TryNew`]: For fallible construction that returns a [`Result`]. Use this
12//! when creating an instance might fail due to validation or other reasons.
13//! - [`New`]: For infallible construction. If creation cannot logically fail
14//! (or if failure should deterministically panic), use this trait.
15//!
16//! # Advanced Construction Patterns
17//!
18//! - [`ConditionallyCreate`]: Provides a `create_conditionally` method that
19//! behaves differently in debug and release builds. In debug, it uses
20//! `TryNew::try_new().expect()`, panicking on failure. In release, it uses
21//! `New::new()`. This is useful for enforcing stricter checks during
22//! development.
23//! - [`ValidationPolicy`]: Defines a contract for validation logic. A policy
24//! specifies how a value should be validated and what error type is
25//! returned upon failure. This allows for reusable validation strategies.
26//! - [`TryNewValidated`]: Extends [`TryNew`] by associating a specific
27//! [`ValidationPolicy`] with the type. The [`TryNewValidated::try_new_validated`] method
28//! first applies the policy and then, if successful, proceeds with the
29//! underlying [`TryNew`] construction logic. This enables a two-phase
30//! construction process: external validation followed by internal creation.
31//!
32//! # Features
33//!
34//! - `std`: (Enabled by default) When enabled, the `Error` associated type
35//! for [`TryNew`] and [`ValidationPolicy`] is bound by `std::error::Error`.
36//! - If `std` is not enabled, their `Error` types are bound by `core::fmt::Debug`.
37//!
38//! # Examples
39//!
40//! See the documentation for individual traits for specific examples.
41
42// Re-export IntoInner for convenience, as it's a supertrait of TryNew.
43pub use into_inner::IntoInner;
44
45use duplicate::duplicate_item;
46use num::Complex;
47use std::convert::Infallible;
48
49/// A trait for creating new instances of a type with fallible validation.
50///
51/// This trait provides a standardized way to create new instances of a type from an
52/// "inner" value, where the construction process might fail (e.g., due to validation rules).
53/// Implementors must also implement [`IntoInner`], which defines the `InnerType` used
54/// for construction.
55///
56/// # Associated Types
57///
58/// - `InnerType`: (From the [`IntoInner`] supertrait) The type of the input value used to
59/// attempt creation of a new instance of `Self`.
60/// - `Error`: The error type that is returned if `try_new` fails. This error type
61/// must implement `std::error::Error` if the `std` feature is enabled.
62/// If the `std` feature is not enabled, it must implement `core::fmt::Debug`.
63///
64/// # Examples
65///
66/// ```rust
67/// use try_create::{TryNew, IntoInner};
68/// # #[cfg(feature = "std")]
69/// # use std::error::Error;
70/// # #[cfg(feature = "std")]
71/// # use core::fmt;
72///
73/// // Define a custom error type
74/// #[derive(Debug, PartialEq)]
75/// struct NotPositiveError;
76///
77/// // Implement Display and Error for the custom error (required if using std::error::Error)
78/// # #[cfg(feature = "std")]
79/// impl fmt::Display for NotPositiveError {
80/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81/// write!(f, "Value must be positive")
82/// }
83/// }
84/// # #[cfg(feature = "std")]
85/// impl Error for NotPositiveError {}
86/// # #[cfg(not(feature = "std"))]
87/// # trait Error: core::fmt::Debug {} // Minimal Error trait for no_std
88/// # #[cfg(not(feature = "std"))]
89/// # impl Error for NotPositiveError {}
90///
91///
92/// // A struct that wraps an i32, ensuring it's positive.
93/// #[derive(Debug, PartialEq)]
94/// struct PositiveInteger {
95/// value: i32,
96/// }
97///
98/// // Implement IntoInner to define what `InnerType` is.
99/// impl IntoInner for PositiveInteger {
100/// type InnerType = i32;
101///
102/// fn into_inner(self) -> Self::InnerType {
103/// self.value
104/// }
105/// }
106///
107/// // Implement TryNew for PositiveInteger.
108/// impl TryNew for PositiveInteger {
109/// type Error = NotPositiveError;
110/// // `InnerType` is `i32`, inherited from `IntoInner`.
111///
112/// fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
113/// if value > 0 {
114/// Ok(PositiveInteger { value })
115/// } else {
116/// Err(NotPositiveError)
117/// }
118/// }
119/// }
120///
121/// // Usage
122/// assert_eq!(PositiveInteger::try_new(10), Ok(PositiveInteger { value: 10 }));
123/// assert_eq!(PositiveInteger::try_new(0), Err(NotPositiveError));
124/// assert_eq!(PositiveInteger::try_new(-5), Err(NotPositiveError));
125///
126/// let positive_num = PositiveInteger::try_new(42).unwrap();
127/// assert_eq!(positive_num.into_inner(), 42);
128/// ```
129pub trait TryNew: Sized + IntoInner {
130 /// The error type that can be returned by the `try_new` method.
131 #[cfg(feature = "std")]
132 type Error: std::error::Error;
133 #[cfg(not(feature = "std"))]
134 type Error: core::fmt::Debug; // Added for no_std consistency
135
136 /// Attempts to create a new instance of `Self` from `value`.
137 ///
138 /// # Parameters
139 ///
140 /// - `value`: The `InnerType` value from which to create the instance.
141 ///
142 /// # Returns
143 ///
144 /// - `Ok(Self)` if the instance is created successfully.
145 /// - `Err(Self::Error)` if creation fails (e.g., due to invalid input).
146 fn try_new(value: Self::InnerType) -> Result<Self, Self::Error>;
147}
148
149/// A trait for creating new instances of a type infallibly.
150///
151/// This trait provides a method for creating new instances of a type from `Self::InnerType`
152/// (defined by the `IntoInner` supertrait).
153///
154/// Implementors should ensure that the [`New`] method cannot fail in a way that would
155/// typically return a `Result`. If invalid input could lead to an unrecoverable state
156/// or violate invariants, [`New`] should panic. For fallible construction that returns
157/// a `Result`, use the [[`TryNew`]] trait.
158///
159/// # Examples
160///
161/// ```rust
162/// use try_create::{New, IntoInner, TryNew}; // Assuming TryNew is used for context or comparison
163/// # #[cfg(feature = "std")]
164/// # use std::error::Error;
165/// # #[cfg(feature = "std")]
166/// # use std::fmt;
167///
168/// // Example struct
169/// #[derive(Debug, PartialEq)]
170/// struct MyType(i32);
171///
172/// // Define a custom error for MyType for the TryNew example
173/// #[derive(Debug, PartialEq)]
174/// struct MyTypeError(String);
175///
176/// // Implement Display and Error for MyTypeError
177/// # #[cfg(feature = "std")]
178/// impl fmt::Display for MyTypeError {
179/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180/// write!(f, "MyTypeError: {}", self.0)
181/// }
182/// }
183///
184/// # #[cfg(feature = "std")]
185/// impl Error for MyTypeError {}
186/// // For no_std, MyTypeError already derives Debug, which is sufficient.
187///
188/// impl IntoInner for MyType {
189/// type InnerType = i32;
190/// fn into_inner(self) -> Self::InnerType { self.0 }
191/// }
192///
193/// // Example TryNew (optional here, but good for context)
194/// impl TryNew for MyType {
195/// type Error = MyTypeError; // Use the custom error type
196/// fn try_new(value: i32) -> Result<Self, Self::Error> {
197/// if value < 0 { Err(MyTypeError("Value cannot be negative".to_string())) }
198/// else { Ok(MyType(value)) }
199/// }
200/// }
201///
202/// impl New for MyType {
203/// fn new(value: i32) -> Self {
204/// if value < 0 {
205/// panic!("MyType::new: Value cannot be negative");
206/// }
207/// MyType(value)
208/// }
209/// }
210///
211/// assert_eq!(MyType::new(10), MyType(10));
212/// // The following would panic:
213/// // MyType::new(-5);
214/// ```
215pub trait New: Sized + IntoInner {
216 /// Creates a new instance of `Self` from `value`.
217 ///
218 /// This method is expected to be infallible in terms of returning a `Result`.
219 /// If the provided `value` cannot produce a valid instance of `Self`
220 /// according to the type's invariants, this method should panic.
221 ///
222 /// # Parameters
223 ///
224 /// - `value`: The `Self::InnerType` value from which to create the instance.
225 fn new(value: Self::InnerType) -> Self;
226}
227
228// It's necessary to import Debug for the bound on TryNew's error type.
229// This is implicitly handled if `std::error::Error` is used (as it requires Debug)
230// or if `core::fmt::Debug` is directly used for no_std.
231// For clarity, if you were in a module that didn't automatically have `std::fmt::Debug`
232// or `core::fmt::Debug` in scope, you might need:
233// use core::fmt::Debug; // or std::fmt::Debug if std is assumed
234
235/// A trait for conditionally creating objects.
236///
237/// In debug mode, it uses `Self::try_new().expect()` to create an instance,
238/// panicking if `try_new` returns an error.
239/// In release mode, it uses `Self::new()` to create an instance.
240///
241/// This trait requires the type to also implement [`TryNew`] and [`New`].
242/// The error associated with [`TryNew`] (`<Self as TryNew>::Error`) must implement [`Debug`].
243///
244/// # Example
245/// ```rust
246/// use try_create::{ConditionallyCreate, TryNew, New, IntoInner};
247/// # // Copied from TryNew example for self-containment, adjust if shared
248/// # #[cfg(feature = "std")]
249/// # use std::error::Error;
250/// # #[cfg(feature = "std")]
251/// # use std::fmt;
252///
253/// #[derive(Debug, PartialEq)]
254/// struct NotPositiveError;
255///
256/// # #[cfg(feature = "std")]
257/// impl fmt::Display for NotPositiveError {
258/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259/// write!(f, "Value must be positive")
260/// }
261/// }
262/// # #[cfg(feature = "std")]
263/// impl Error for NotPositiveError {}
264/// # #[cfg(not(feature = "std"))]
265/// # trait Error: core::fmt::Debug {} // Minimal Error trait for no_std
266/// # #[cfg(not(feature = "std"))]
267/// # impl Error for NotPositiveError {}
268///
269///
270/// #[derive(Debug, PartialEq)]
271/// struct PositiveInteger { value: i32 }
272///
273/// impl IntoInner for PositiveInteger {
274/// type InnerType = i32;
275/// fn into_inner(self) -> Self::InnerType { self.value }
276/// }
277///
278/// impl TryNew for PositiveInteger {
279/// type Error = NotPositiveError;
280/// fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
281/// if value > 0 { Ok(PositiveInteger { value }) }
282/// else { Err(NotPositiveError) }
283/// }
284/// }
285///
286/// // To use ConditionallyCreate, PositiveInteger must also implement New.
287/// impl New for PositiveInteger {
288/// fn new(value: Self::InnerType) -> Self {
289/// match Self::try_new(value) {
290/// Ok(instance) => instance,
291/// Err(e) => panic!("PositiveInteger::new failed for a non-positive value. Error: {:?}", e),
292/// }
293/// }
294/// }
295///
296/// // Now you can call:
297/// # fn example_usage() { // Encapsulate in a function for the doctest
298/// let val_ok = 10;
299/// let _p1 = PositiveInteger::create_conditionally(val_ok);
300/// assert_eq!(_p1, PositiveInteger { value: 10 });
301///
302/// // In debug, this would panic (difficult to test directly in doctest without specific harness):
303/// // let val_err = -5;
304/// // let _p2 = PositiveInteger::create_conditionally(val_err);
305/// # }
306/// # example_usage();
307/// ```
308pub trait ConditionallyCreate: Sized + TryNew + New
309where
310 <Self as TryNew>::Error: core::fmt::Debug,
311{
312 /// Conditionally creates an instance of `Self`.
313 ///
314 /// # Parameters
315 ///
316 /// - `value`: The `Self::InnerType` value (defined by `IntoInner`)
317 /// from which to create the instance.
318 ///
319 /// # Panics
320 ///
321 /// In debug mode, this method will panic if `Self::try_new(value)`
322 /// returns `Err`. The panic message will include the error's description.
323 /// In release mode, the panic behavior depends on the implementation
324 /// of `Self::new(value)`.
325 fn create_conditionally(value: Self::InnerType) -> Self {
326 #[cfg(debug_assertions)]
327 {
328 // In debug mode, use try_new and panic on error.
329 Self::try_new(value).expect("ConditionallyCreate: try_new() failed in debug mode")
330 }
331 #[cfg(not(debug_assertions))]
332 {
333 // In release mode, use new (which is assumed to be infallible
334 // or to handle failure internally, e.g., by panicking).
335 Self::new(value)
336 }
337 }
338}
339
340// Blanket implementation of `ConditionallyCreate` for all types `T`
341// that implement [`TryNew`] and [`New`], and whose `TryNew::Error` type
342// implements `Debug`.
343impl<T> ConditionallyCreate for T
344where
345 T: TryNew + New,
346 <T as TryNew>::Error: core::fmt::Debug, // This bound is inherited from the trait definition
347{
348 // The `create_conditionally` method already has a default implementation in the trait,
349 // so it doesn't need to be redefined here.
350}
351
352/// Defines a contract for validation policies.
353///
354/// A validation policy specifies how a value of a certain type (`Value`)
355/// should be validated and what error type (`Error`) is returned upon failure.
356///
357/// # Associated Types
358///
359/// - `Value`: The type of the value to be validated.
360/// - `Error`: The type of the error returned if validation fails. This error type
361/// must implement [`std::error::Error`] if the `std` feature is enabled.
362/// If the `std` feature is not enabled, it must implement [`core::fmt::Debug`].
363pub trait ValidationPolicy {
364 /// The type of the value to be validated.
365 type Value;
366
367 /// The type of the error returned if validation fails.
368 #[cfg(feature = "std")]
369 type Error: std::error::Error;
370 #[cfg(not(feature = "std"))]
371 type Error: core::fmt::Debug; // For no_std consistency
372
373 /// Validates a value by consuming it.
374 ///
375 /// If validation is successful, the original value is returned.
376 /// Otherwise, an error is returned.
377 /// This default implementation calls [`Self::validate_ref()]`.
378 fn validate(v: Self::Value) -> Result<Self::Value, Self::Error> {
379 Self::validate_ref(&v)?;
380 Ok(v)
381 }
382
383 /// Validates a value by reference.
384 ///
385 /// Returns `Ok(())` if validation is successful, otherwise an error is returned.
386 /// This method must be implemented by concrete policies.
387 fn validate_ref(v: &Self::Value) -> Result<(), Self::Error>;
388}
389
390/// A trait for creating new instances of a type with fallible validation,
391/// where the validation logic is defined by an associated `ValidationPolicy`.
392///
393/// This trait extends [`TryNew`] by associating a specific [`ValidationPolicy`]
394/// with the type. The [`Self::try_new_validated()`] method first uses this policy to validate
395/// the input value. If validation succeeds, it then typically calls the underlying
396/// `try_new` method (or a similar construction logic) to create the instance.
397///
398/// # Supertraits
399///
400/// - [`TryNew`]: Implementors of `TryNewValidated` must also implement `TryNew`.
401/// This means they must define an `InnerType` (via the [`IntoInner`] supertrait of `TryNew`),
402/// an `Error` type, and a `try_new` method.
403pub trait TryNewValidated: TryNew {
404 /// An implementor of [`ValidationPolicy`].
405 /// - The `Value` associated type of this `Policy` must be the same as the
406 /// `InnerType` of `Self` (i.e., `Self::Policy::Value == <Self as IntoInner>::InnerType`).
407 /// - The `Error` associated type of this `Policy` must be convertible into
408 /// `Self::Error` (the error type defined by the `TryNew` trait for `Self`).
409 /// This is ensured by the `From` bound on `Self::Error`.
410 type Policy: ValidationPolicy<Value = <Self as IntoInner>::InnerType>;
411
412 /// Attempts to create a new instance of `Self` from `value`.
413 /// This method should first validate `value` using `Self::Policy::validate_ref(&value)`
414 /// (or `Self::Policy::validate(value)` if consuming the value is intended by the policy).
415 /// If validation is successful, it proceeds to construct the `Self` instance,
416 /// typically by calling `Self::try_new(value)`.
417 /// If validation fails, the error from the policy is converted into `Self::Error` and returned.
418 /// If construction after successful validation fails, the error from `Self::try_new` is returned.
419 fn try_new_validated(
420 value: <Self as IntoInner>::InnerType,
421 ) -> Result<Self, Self::Error>
422 where
423 Self: Sized;
424}
425
426#[duplicate_item(
427 T;
428 [f32];
429 [f64];
430 [i8];
431 [i16];
432 [i32];
433 [i64];
434 [i128];
435 [u8];
436 [u16];
437 [u32];
438 [u64];
439 [u128];
440 [isize];
441 [usize];
442 [bool];
443 [char];
444 [Complex<f32>];
445 [Complex<f64>];
446)]
447impl New for T {
448 /// Creates a new instance of the fundamental type from the given value.
449 ///
450 /// This implementation is infallible and simply returns the input value.
451 fn new(value: Self::InnerType) -> Self {
452 value
453 }
454}
455
456#[duplicate_item(
457 T;
458 [f32];
459 [f64];
460 [i8];
461 [i16];
462 [i32];
463 [i64];
464 [i128];
465 [u8];
466 [u16];
467 [u32];
468 [u64];
469 [u128];
470 [isize];
471 [usize];
472 [bool];
473 [char];
474 [Complex<f32>];
475 [Complex<f64>];
476)]
477impl TryNew for T {
478 type Error = Infallible; // No error type needed for fundamental types
479
480 /// Creates a new instance of the fundamental type from the given value.
481 ///
482 /// This implementation is infallible and simply returns the input value.
483 fn try_new(value: Self::InnerType) -> Result<Self, Infallible> {
484 Ok(value)
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*; // Import traits from the parent module (your library)
491
492 // --- Test setup for PositiveInteger ---
493 #[derive(Debug, PartialEq)]
494 struct TestNotPositiveError;
495
496 // Implement Display and Error for TestNotPositiveError for std builds
497 #[cfg(feature = "std")]
498 impl std::fmt::Display for TestNotPositiveError {
499 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
500 write!(f, "Value must be positive (Test Error)")
501 }
502 }
503
504 #[cfg(feature = "std")]
505 impl std::error::Error for TestNotPositiveError {}
506
507 // For no_std, deriving Debug is sufficient as per TryNew's Error bound.
508
509 #[derive(Debug, PartialEq)]
510 struct TestPositiveInteger {
511 value: i32,
512 }
513
514 impl IntoInner for TestPositiveInteger {
515 type InnerType = i32;
516 fn into_inner(self) -> Self::InnerType {
517 self.value
518 }
519 }
520
521 impl TryNew for TestPositiveInteger {
522 type Error = TestNotPositiveError;
523 fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
524 if value > 0 {
525 Ok(TestPositiveInteger { value })
526 } else {
527 Err(TestNotPositiveError)
528 }
529 }
530 }
531
532 impl New for TestPositiveInteger {
533 fn new(value: Self::InnerType) -> Self {
534 // Consistent with the example: panic if try_new would fail
535 match Self::try_new(value) {
536 Ok(instance) => instance,
537 Err(_) => panic!("TestPositiveInteger::new: Value must be positive."),
538 }
539 }
540 }
541
542 mod try_new {
543 use super::*; // Import traits from the parent module (your library)
544
545 // --- Tests for PositiveInteger ---
546 #[test]
547 fn test_positive_integer_try_new_ok() {
548 assert_eq!(
549 TestPositiveInteger::try_new(10),
550 Ok(TestPositiveInteger { value: 10 })
551 );
552 }
553
554 #[test]
555 fn test_positive_integer_try_new_err_zero() {
556 assert_eq!(TestPositiveInteger::try_new(0), Err(TestNotPositiveError));
557 }
558
559 #[test]
560 fn test_positive_integer_try_new_err_negative() {
561 assert_eq!(TestPositiveInteger::try_new(-5), Err(TestNotPositiveError));
562 }
563
564 // --- Tests for MyType ---
565 #[test]
566 fn test_my_type_try_new_ok() {
567 assert_eq!(TestMyType::try_new(5), Ok(TestMyType(5)));
568 }
569
570 #[test]
571 fn test_my_type_try_new_err() {
572 assert_eq!(
573 TestMyType::try_new(-1),
574 Err(TestMyTypeError("Value cannot be negative".to_string()))
575 );
576 }
577 } // mod try_new
578
579 mod new {
580 use super::*; // Import traits from the parent module (your library)
581
582 #[test]
583 fn test_positive_integer_new_ok() {
584 assert_eq!(
585 TestPositiveInteger::new(10),
586 TestPositiveInteger { value: 10 }
587 );
588 }
589
590 #[test]
591 #[should_panic(expected = "TestPositiveInteger::new: Value must be positive.")]
592 fn test_positive_integer_new_panic_zero() {
593 TestPositiveInteger::new(0);
594 }
595
596 #[test]
597 #[should_panic(expected = "TestPositiveInteger::new: Value must be positive.")]
598 fn test_positive_integer_new_panic_negative() {
599 TestPositiveInteger::new(-10);
600 }
601
602 #[test]
603 fn test_my_type_new_ok() {
604 assert_eq!(TestMyType::new(100), TestMyType(100));
605 }
606
607 #[test]
608 #[should_panic(expected = "TestMyType::new: Value cannot be negative")]
609 fn test_my_type_new_panic() {
610 TestMyType::new(-1);
611 }
612 } // mod new
613
614 mod into_inner {
615 use super::*;
616
617 #[test]
618 fn test_positive_integer_into_inner() {
619 let pi = TestPositiveInteger::new(42);
620 assert_eq!(pi.into_inner(), 42);
621 }
622
623 #[test]
624 fn test_my_type_into_inner() {
625 let mt = TestMyType::new(7);
626 assert_eq!(mt.into_inner(), 7);
627 }
628 }
629
630 // --- Test setup for MyType (from New example) ---
631 #[derive(Debug, PartialEq)]
632 struct TestMyTypeError(String);
633
634 #[cfg(feature = "std")]
635 impl std::fmt::Display for TestMyTypeError {
636 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637 write!(f, "TestMyTypeError: {}", self.0)
638 }
639 }
640
641 #[cfg(feature = "std")]
642 impl std::error::Error for TestMyTypeError {}
643
644 #[derive(Debug, PartialEq)]
645 struct TestMyType(i32);
646
647 impl IntoInner for TestMyType {
648 type InnerType = i32;
649 fn into_inner(self) -> Self::InnerType {
650 self.0
651 }
652 }
653
654 impl TryNew for TestMyType {
655 type Error = TestMyTypeError;
656 fn try_new(value: i32) -> Result<Self, Self::Error> {
657 if value < 0 {
658 Err(TestMyTypeError("Value cannot be negative".to_string()))
659 } else {
660 Ok(TestMyType(value))
661 }
662 }
663 }
664
665 impl New for TestMyType {
666 fn new(value: i32) -> Self {
667 if value < 0 {
668 panic!("TestMyType::new: Value cannot be negative");
669 }
670 TestMyType(value)
671 }
672 }
673
674 mod conditionally_create {
675 use super::*;
676
677 #[test]
678 #[cfg(debug_assertions)] // This test specifically targets the debug behavior
679 fn test_positive_integer_conditionally_create_debug_ok() {
680 assert_eq!(
681 TestPositiveInteger::create_conditionally(10),
682 TestPositiveInteger { value: 10 }
683 );
684 }
685
686 #[test]
687 #[cfg(debug_assertions)] // This test specifically targets the debug behavior
688 #[should_panic(expected = "ConditionallyCreate: try_new() failed in debug mode")]
689 fn test_positive_integer_conditionally_create_debug_panic() {
690 TestPositiveInteger::create_conditionally(-5);
691 }
692
693 #[test]
694 #[cfg(not(debug_assertions))] // This test specifically targets the release behavior
695 fn test_positive_integer_conditionally_create_release_ok() {
696 assert_eq!(
697 TestPositiveInteger::create_conditionally(10),
698 TestPositiveInteger { value: 10 }
699 );
700 }
701
702 #[test]
703 #[cfg(not(debug_assertions))] // This test specifically targets the release behavior
704 #[should_panic(expected = "TestPositiveInteger::new: Value must be positive.")]
705 fn test_positive_integer_conditionally_create_release_panic() {
706 // In release, create_conditionally calls new(), which for TestPositiveInteger panics.
707 TestPositiveInteger::create_conditionally(-5);
708 }
709
710 #[test]
711 #[cfg(debug_assertions)]
712 fn test_my_type_conditionally_create_debug_ok() {
713 assert_eq!(TestMyType::create_conditionally(20), TestMyType(20));
714 }
715
716 #[test]
717 #[cfg(debug_assertions)]
718 #[should_panic(expected = "ConditionallyCreate: try_new() failed in debug mode")]
719 fn test_my_type_conditionally_create_debug_panic() {
720 TestMyType::create_conditionally(-3);
721 }
722
723 #[test]
724 #[cfg(not(debug_assertions))]
725 fn test_my_type_conditionally_create_release_ok() {
726 assert_eq!(TestMyType::create_conditionally(20), TestMyType(20));
727 }
728
729 #[test]
730 #[cfg(not(debug_assertions))]
731 #[should_panic(expected = "TestMyType::new: Value cannot be negative")]
732 fn test_my_type_conditionally_create_release_panic() {
733 TestMyType::create_conditionally(-3);
734 }
735 }
736
737 mod try_new_validated {
738 use super::*;
739
740 // --- Test setup for ValidationPolicy and TryNewValidated ---
741
742 // 1. Define a concrete ValidationPolicy
743 #[derive(Debug, PartialEq)]
744 struct TestPolicyError(String);
745
746 #[cfg(feature = "std")]
747 impl std::fmt::Display for TestPolicyError {
748 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749 write!(f, "TestPolicyError: {}", self.0)
750 }
751 }
752
753 #[cfg(feature = "std")]
754 impl std::error::Error for TestPolicyError {}
755
756 struct MinValuePolicy {
757 min_value: i32,
758 }
759
760 impl ValidationPolicy for MinValuePolicy {
761 type Value = i32;
762 type Error = TestPolicyError;
763
764 fn validate_ref(v: &Self::Value) -> Result<(), Self::Error> {
765 // For this test, let's imagine a global or static policy.
766 // A real policy might take its configuration (e.g., min_value) at construction.
767 // For simplicity, we'll hardcode a value or use a fixed one.
768 // Let's make it simple: value must be >= 0 for this policy.
769 if *v >= 0 {
770 Ok(())
771 } else {
772 Err(TestPolicyError("Value must be non-negative.".to_string()))
773 }
774 }
775 // `validate` method uses the default implementation.
776 }
777
778 // 2. Define a concrete type that implements TryNewValidated
779
780 #[derive(Debug, PartialEq)]
781 enum TestValidatedTypeError {
782 Policy(TestPolicyError),
783 Construction(String),
784 }
785
786 #[cfg(feature = "std")]
787 impl std::fmt::Display for TestValidatedTypeError {
788 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
789 match self {
790 TestValidatedTypeError::Policy(e) => write!(f, "Policy error: {}", e),
791 TestValidatedTypeError::Construction(s) => {
792 write!(f, "Construction error: {}", s)
793 }
794 }
795 }
796 }
797
798 #[cfg(feature = "std")]
799 impl std::error::Error for TestValidatedTypeError {
800 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
801 match self {
802 TestValidatedTypeError::Policy(e) => Some(e),
803 TestValidatedTypeError::Construction(_) => None,
804 }
805 }
806 }
807 // No-std: Debug is derived.
808
809 impl From<TestPolicyError> for TestValidatedTypeError {
810 fn from(e: TestPolicyError) -> Self {
811 TestValidatedTypeError::Policy(e)
812 }
813 }
814
815 #[derive(Debug, PartialEq)]
816 struct MyValidatedType {
817 value: i32,
818 }
819
820 impl IntoInner for MyValidatedType {
821 type InnerType = i32;
822 fn into_inner(self) -> Self::InnerType {
823 self.value
824 }
825 }
826
827 impl TryNew for MyValidatedType {
828 type Error = TestValidatedTypeError;
829
830 fn try_new(value: Self::InnerType) -> Result<Self, Self::Error> {
831 // Inner construction logic: value must be > 10
832 if value > 10 {
833 Ok(MyValidatedType { value })
834 } else {
835 Err(TestValidatedTypeError::Construction(
836 "Value must be greater than 10 for construction.".to_string(),
837 ))
838 }
839 }
840 }
841
842 impl TryNewValidated for MyValidatedType {
843 type Policy = MinValuePolicy; // Using the policy defined above
844
845 fn try_new_validated(value: <Self as IntoInner>::InnerType) -> Result<Self, Self::Error>
846 where
847 Self: Sized,
848 Self::Error: From<<Self::Policy as ValidationPolicy>::Error>,
849 {
850 // 1. Validate using the policy. The `?` handles error conversion.
851 Self::Policy::validate_ref(&value)?;
852
853 // 2. If policy validation passes, proceed with `try_new`.
854 Self::try_new(value)
855 }
856 }
857
858 // --- Tests for ValidationPolicy (using MinValuePolicy) ---
859 #[test]
860 fn validation_policy_validate_ref_ok() {
861 assert_eq!(MinValuePolicy::validate_ref(&5), Ok(()));
862 assert_eq!(MinValuePolicy::validate_ref(&0), Ok(()));
863 }
864
865 #[test]
866 fn validation_policy_validate_ref_err() {
867 assert_eq!(
868 MinValuePolicy::validate_ref(&-5),
869 Err(TestPolicyError("Value must be non-negative.".to_string()))
870 );
871 }
872
873 #[test]
874 fn validation_policy_validate_consuming_ok() {
875 // Tests the default `validate` method
876 assert_eq!(MinValuePolicy::validate(5), Ok(5));
877 assert_eq!(MinValuePolicy::validate(0), Ok(0));
878 }
879
880 #[test]
881 fn validation_policy_validate_consuming_err() {
882 // Tests the default `validate` method
883 assert_eq!(
884 MinValuePolicy::validate(-5),
885 Err(TestPolicyError("Value must be non-negative.".to_string()))
886 );
887 }
888
889 // --- Tests for TryNewValidated (using MyValidatedType) ---
890 #[test]
891 fn try_new_validated_success() {
892 // Policy (>=0) passes for 15. try_new (>10) passes for 15.
893 assert_eq!(
894 MyValidatedType::try_new_validated(15),
895 Ok(MyValidatedType { value: 15 })
896 );
897 }
898
899 #[test]
900 fn try_new_validated_policy_fail() {
901 // Policy (>=0) fails for -5.
902 let result = MyValidatedType::try_new_validated(-5);
903 assert!(matches!(result, Err(TestValidatedTypeError::Policy(..))));
904 }
905
906 #[test]
907 fn try_new_validated_construction_fail_after_policy_pass() {
908 // Policy (>=0) passes for 5. try_new (>10) fails for 5.
909 let result = MyValidatedType::try_new_validated(5);
910 assert!(matches!(
911 result,
912 Err(TestValidatedTypeError::Construction(..))
913 ));
914 }
915
916 #[test]
917 fn try_new_validated_policy_pass_construction_pass_edge() {
918 // Policy (>=0) passes for 11. try_new (>10) passes for 11.
919 assert_eq!(
920 MyValidatedType::try_new_validated(11),
921 Ok(MyValidatedType { value: 11 })
922 );
923 }
924
925 #[test]
926 fn try_new_validated_policy_pass_construction_fail_edge() {
927 // Policy (>=0) passes for 10. try_new (>10) fails for 10.
928 let result = MyValidatedType::try_new_validated(10);
929 assert!(matches!(
930 result,
931 Err(TestValidatedTypeError::Construction(..))
932 ));
933 }
934 }
935}