Skip to main content

maybe_valid/
api.rs

1/// A type whose values are guaranteed to satisfy a validation predicate.
2///
3/// Implementing `Validated` marks `Self` as the *validated form* of some
4/// underlying data — typically a newtype around a raw type, or a `?Sized`
5/// view type like [`str`] over [`[u8]`]. The implementation asserts that
6/// every value of `Self` that can be constructed through the crate's
7/// conversion traits satisfies the type's invariant.
8///
9/// This trait does not itself perform validation. It declares the
10/// *canonical failure explanation* for the type, via the [`InvalidReason`]
11/// associated type. Conversions into `Self` — whether borrowing
12/// ([`AsValidated`]) or owning ([`IntoValidated`]) — report failure
13/// using this type.
14///
15/// [`InvalidReason`]: Validated::InvalidReason
16/// [`AsValidated`]: crate::AsValidated
17/// [`IntoValidated`]: crate::IntoValidated
18///
19/// # Scope
20///
21/// This trait models structural refinement — the validated type shares
22/// a representation with its precursor and differs only in which values
23/// are permitted. It is not intended for general fallible conversion,
24/// such as parsing a string into a binary value with different layout.
25/// For those cases, use [`FromStr`] or [`TryFrom`].
26///
27/// # The role of `InvalidReason`
28///
29/// `InvalidReason` describes *why* a candidate value failed to satisfy
30/// the predicate. It is purely diagnostic: it must not carry the
31/// candidate value itself.
32///
33/// This restriction is deliberate. Precursor recovery — giving the
34/// caller back their unvalidated input on failure — is handled
35/// structurally by the conversion traits:
36///
37/// - [`AsValidated::as_validated`] returns a reference into `&self`,
38///   so the caller retains access to the precursor through their
39///   existing binding.
40/// - [`IntoValidated::into_validated`] returns the owned precursor
41///   alongside the `InvalidReason` in the invalid branch of
42///   [`MaybeValidOwned`].
43///
44/// Keeping `InvalidReason` precursor-free allows a single
45/// `Validated` impl to serve both borrowing and owning conversions
46/// without duplicating data or forcing a clone on the owning path.
47///
48/// [`MaybeValidOwned`]: crate::MaybeValidOwned
49///
50/// # Contract
51///
52/// Implementers of `Validated` promise the following:
53///
54/// 1. **Predicate stability.** Whether a value of the underlying type
55///    satisfies the predicate must depend only on the value's observable
56///    state, not on external state, time, or interior mutability. A value
57///    that was valid yesterday is valid today.
58///
59/// 2. **`InvalidReason` is diagnostic only.** The `InvalidReason` type
60///    must not contain the candidate value or a copy of it. It may
61///    contain positional information (byte offsets, field indices),
62///    expected-versus-actual summaries, or any other explanation,
63///    provided these are cheap to construct relative to the cost of
64///    validation itself.
65///
66/// 3. **`InvalidReason` is cheaply constructible.** Constructing an
67///    `InvalidReason` on the failure path should not be dramatically
68///    more expensive than running the validation itself. In particular,
69///    it should not allocate proportional to the candidate's size.
70///
71/// These are logical contracts, not compiler-enforced ones. Violating
72/// them will not cause undefined behavior, but will break the guarantees
73/// that generic code written against `Validated` relies on.
74///
75/// # Examples
76///
77/// The canonical example is [`str`] as the validated form of `[u8]`:
78///
79/// ```
80/// # use maybe_valid::Validated;
81/// # use std::str::Utf8Error;
82/// fn assert_validated_utf8<T: Validated<InvalidReason = Utf8Error> + ?Sized>() {}
83/// assert_validated_utf8::<str>();
84/// ```
85///
86/// `Utf8Error` carries a `valid_up_to: usize` and an `error_len:
87/// Option<u8>`. It explains where UTF-8 validation failed, without
88/// holding the `[u8]` that failed — the caller retains that through
89/// their own binding or through [`IntoValidated`]'s return.
90///
91/// A custom newtype over `[u8]` restricting to ASCII:
92///
93/// ```
94/// # use maybe_valid::Validated;
95/// #[repr(transparent)]
96/// pub struct Ascii([u8]);
97///
98/// pub struct NonAsciiReason {
99///     /// Byte offset of the first non-ASCII byte.
100///     pub position: usize,
101///     /// The offending byte value.
102///     pub byte: u8,
103/// }
104///
105/// impl Validated for Ascii {
106///     type InvalidReason = NonAsciiReason;
107/// }
108/// ```
109///
110/// A refinement of an integer type:
111///
112/// ```
113/// # use maybe_valid::{Validated, ZeroReason};
114/// # use std::num::NonZeroU32;
115/// fn assert_nonzero_reason<T: Validated<InvalidReason = ZeroReason>>() {}
116/// assert_nonzero_reason::<NonZeroU32>();
117/// ```
118///
119/// # When *not* to implement `Validated`
120///
121/// `Validated` is not a general-purpose "this type has an invariant"
122/// marker. Implement it only when:
123///
124/// - The type is the canonical validated form of some underlying data,
125///   in the sense that asking "is this underlying value a valid `Self`?"
126///   is a meaningful question with a single canonical predicate.
127/// - You intend to provide [`AsValidated`] or [`IntoValidated`] impls
128///   from one or more precursor types. A `Validated` impl with no
129///   corresponding conversions is inert.
130///
131/// Types with multiple equally canonical predicates (e.g., a byte slice
132/// that might be validated as UTF-8, as ASCII, or as valid JSON
133/// depending on context) should be modeled by having *multiple*
134/// validated target types, each implementing `Validated` with its own
135/// `InvalidReason`, rather than a single type with a configurable
136/// predicate.
137///
138/// # Relationship to other traits
139///
140/// `Validated` occupies a different niche from:
141///
142/// - [`TryFrom`]: expresses any fallible conversion with a
143///   caller-chosen error type. `Validated` pins the error type to the
144///   target and specifies its role (diagnostic only).
145/// - [`FromStr`]: fallible parsing from `&str` with an associated
146///   `Err`. Similar shape, but single-source and not a trait family
147///   for both borrowing and owning conversions.
148/// - [`Borrow`]: infallible borrowing with a `Hash`/`Eq`/`Ord`
149///   agreement contract. `Validated` makes no hash-agreement claim;
150///   conversions are permitted to produce views with different hashing
151///   semantics.
152///
153/// [`TryFrom`]: core::convert::TryFrom
154/// [`FromStr`]: core::str::FromStr
155/// [`Borrow`]: core::borrow::Borrow
156pub trait Validated {
157    /// The explanation returned when a candidate value fails to
158    /// satisfy this type's validation predicate.
159    ///
160    /// Must be diagnostic-only: see the trait-level contract for
161    /// the restrictions on what this type may contain.
162    type InvalidReason;
163}
164
165/// The outcome of borrowing a value as a validated view of type `V`.
166///
167/// Returned by [`AsValidated::as_validated`]. Both variants carry a
168/// reference into the caller's original value — the `Valid` variant
169/// as `&V`, the `Invalid` variant as `&P` (the precursor). The
170/// `Invalid` variant additionally carries the diagnostic reason.
171///
172/// [`AsValidated::as_validated`]: crate::AsValidated::as_validated
173///
174/// # Why the precursor is repeated in `Invalid`
175///
176/// On the invalid path, the caller could reach the precursor through
177/// their existing `&self` binding; including it in the `Invalid`
178/// variant is structurally redundant. It is retained anyway because:
179///
180/// - The type then honestly describes both outcomes as "a reference
181///   into the caller's value, plus (on the invalid path) a reason."
182///   Readers do not have to infer the precursor's availability from
183///   context.
184///
185/// - The shape mirrors [`MaybeValidOwned`], where the precursor must
186///   be returned structurally because consumption would otherwise lose
187///   it. Generic code and documentation can describe both enums in
188///   parallel.
189///
190/// - The cost is a pointer-sized move, not a clone or allocation.
191///
192/// [`MaybeValidOwned`]: crate::MaybeValidOwned
193///
194/// # Why both variants matter
195///
196/// `MaybeValidRef` is deliberately not a [`Result`]. The `Invalid`
197/// variant is not an error to handle and discard: it is a structured
198/// peer of `Valid`, describing the state of data the caller may wish
199/// to continue working with (rendering a partial view, producing a
200/// repair, emitting a diagnostic that references the caller's own
201/// bytes).
202///
203/// # Examples
204///
205/// ```
206/// # use maybe_valid::{AsValidated, MaybeValidRef};
207/// let bytes: &[u8] = b"hello";
208/// let validated: MaybeValidRef<'_, str, [u8]> = bytes.as_validated();
209/// match validated {
210///     MaybeValidRef::Valid(s) => assert_eq!(s, "hello"),
211///     MaybeValidRef::Invalid(bytes, reason) => {
212///         eprintln!(
213///             "invalid at byte {} of {} total",
214///             reason.valid_up_to(),
215///             bytes.len(),
216///         );
217///     }
218/// }
219/// ```
220///
221/// # Construction
222///
223/// `MaybeValidRef` values are produced by [`AsValidated`]
224/// implementations. Direct construction is public and unrestricted:
225/// `V`'s own invariants are enforced by `V`, not by this enum.
226///
227/// [`AsValidated`]: crate::AsValidated
228pub enum MaybeValidRef<'a, V: Validated + ?Sized, P: ?Sized> {
229    /// The borrowed value satisfies `V`'s predicate.
230    Valid(&'a V),
231
232    /// The borrowed value does not satisfy `V`'s predicate.
233    ///
234    /// Holds a reference to the original precursor (aliasing the
235    /// caller's value) and the diagnostic reason.
236    Invalid(&'a P, V::InvalidReason),
237}
238
239impl<'a, V: Validated + ?Sized, P: ?Sized> MaybeValidRef<'a, V, P> {
240    /// Returns `true` if this is the `Valid` variant.
241    pub fn is_valid(&self) -> bool {
242        matches!(self, MaybeValidRef::Valid(_))
243    }
244
245    /// Returns `true` if this is the `Invalid` variant.
246    pub fn is_invalid(&self) -> bool {
247        matches!(self, MaybeValidRef::Invalid(_, _))
248    }
249
250    /// Returns the validated reference, or `None` if invalid.
251    pub fn valid(self) -> Option<&'a V> {
252        match self {
253            MaybeValidRef::Valid(v) => Some(v),
254            MaybeValidRef::Invalid(_, _) => None,
255        }
256    }
257
258    /// Returns the precursor reference on the invalid path, or `None`
259    /// if valid.
260    pub fn invalid_precursor(self) -> Option<&'a P> {
261        match self {
262            MaybeValidRef::Valid(_) => None,
263            MaybeValidRef::Invalid(p, _) => Some(p),
264        }
265    }
266
267    /// Returns the invalid reason, or `None` if valid.
268    ///
269    /// Discards the precursor reference; use [`invalid_parts`] to
270    /// retain both.
271    ///
272    /// [`invalid_parts`]: MaybeValidRef::invalid_parts
273    pub fn invalid_reason(self) -> Option<V::InvalidReason> {
274        match self {
275            MaybeValidRef::Valid(_) => None,
276            MaybeValidRef::Invalid(_, r) => Some(r),
277        }
278    }
279
280    /// Returns the precursor reference and reason on the invalid path,
281    /// or `None` if valid.
282    pub fn invalid_parts(self) -> Option<(&'a P, V::InvalidReason)> {
283        match self {
284            MaybeValidRef::Valid(_) => None,
285            MaybeValidRef::Invalid(p, r) => Some((p, r)),
286        }
287    }
288
289    /// Returns a `MaybeValidRef` that borrows from this one, with the
290    /// same variant structure.
291    ///
292    /// Useful when a caller holds a `MaybeValidRef` by value but needs
293    /// to inspect it without consuming it. The returned value borrows
294    /// the precursor/validated references from `self` and clones the
295    /// `InvalidReason` on the invalid path.
296    pub fn as_ref(&self) -> MaybeValidRef<'_, V, P>
297    where
298        V::InvalidReason: Clone,
299    {
300        match self {
301            MaybeValidRef::Valid(v) => MaybeValidRef::Valid(v),
302            MaybeValidRef::Invalid(p, r) => MaybeValidRef::Invalid(p, r.clone()),
303        }
304    }
305
306    /// Converts into a `Result`, discarding the peer framing and
307    /// bundling the precursor reference into the error.
308    ///
309    /// Useful when integrating with code written against `Result` and
310    /// `?`, at the cost of the explicit-match ergonomics
311    /// `MaybeValidRef` encourages.
312    pub fn into_result(self) -> Result<&'a V, (&'a P, V::InvalidReason)> {
313        match self {
314            MaybeValidRef::Valid(v) => Ok(v),
315            MaybeValidRef::Invalid(p, r) => Err((p, r)),
316        }
317    }
318
319    /// Converts into a `Result` that carries only the reason on the
320    /// error path, discarding the precursor reference.
321    ///
322    /// Prefer [`into_result`] when the precursor is still useful to
323    /// the caller; this method is a convenience for call sites that
324    /// only need the diagnostic.
325    ///
326    /// [`into_result`]: MaybeValidRef::into_result
327    pub fn into_result_reason_only(self) -> Result<&'a V, V::InvalidReason> {
328        match self {
329            MaybeValidRef::Valid(v) => Ok(v),
330            MaybeValidRef::Invalid(_, r) => Err(r),
331        }
332    }
333}
334
335#[cfg(feature = "alloc")]
336impl<'a, V, P> MaybeValidRef<'a, V, P>
337where
338    V: Validated + ::alloc::borrow::ToOwned + ?Sized,
339    V::Owned: Validated<InvalidReason = V::InvalidReason>,
340    P: ::alloc::borrow::ToOwned + ?Sized,
341{
342    /// Produces an owned version of this outcome by cloning the
343    /// borrowed `V` (or `P`) into its owned form.
344    pub fn into_owned(self) -> MaybeValidOwned<V::Owned, P::Owned> {
345        match self {
346            MaybeValidRef::Valid(v) => MaybeValidOwned::Valid(v.to_owned()),
347            MaybeValidRef::Invalid(p, r) => MaybeValidOwned::Invalid(p.to_owned(), r),
348        }
349    }
350}
351
352/// The outcome of consuming a value into a validated form of type `V`.
353///
354/// Returned by [`IntoValidated::into_validated`]. The `Valid` variant
355/// holds the constructed `V`; the `Invalid` variant holds the original
356/// precursor (returned unchanged, by move) alongside the diagnostic.
357///
358/// [`IntoValidated::into_validated`]: crate::IntoValidated::into_validated
359///
360/// # Why both variants matter
361///
362/// `MaybeValidOwned` is deliberately not a [`Result`]. The `Invalid`
363/// variant is not an error to handle and discard: it returns the
364/// caller's input to them, intact, so they can retry, repair, log,
365/// or fall through to an alternative. Routing this through `Result`
366/// would frame precursor recovery as error-handling boilerplate;
367/// `MaybeValidOwned` frames it as a first-class outcome.
368///
369/// The precursor is returned by move, not by clone. The trait does
370/// not require `Self: Clone`, and no allocation or duplication
371/// occurs on the invalid path beyond what constructing the
372/// diagnostic requires.
373///
374/// # Examples
375///
376/// ```
377/// # #[cfg(feature = "alloc")]
378/// # {
379/// # use maybe_valid::{IntoValidated, MaybeValidOwned};
380/// let bytes = vec![0xff, 0xfe];
381/// let validated: MaybeValidOwned<String, Vec<u8>> = bytes.into_validated();
382/// match validated {
383///     MaybeValidOwned::Valid(s) => println!("got: {}", s),
384///     MaybeValidOwned::Invalid(bytes, reason) => {
385///         // `bytes` is the original Vec<u8>, moved back to us.
386///         assert_eq!(bytes, vec![0xff, 0xfe]);
387///         eprintln!("invalid at byte {}", reason.valid_up_to());
388///     }
389/// }
390/// # }
391/// ```
392///
393/// # Construction
394///
395/// `MaybeValidOwned` values are produced by [`IntoValidated`]
396/// implementations. Direct construction is public and unrestricted:
397/// `V`'s own invariants are enforced by `V`, not by this enum.
398///
399/// [`IntoValidated`]: crate::IntoValidated
400pub enum MaybeValidOwned<V: Validated, P> {
401    /// The precursor satisfied `V`'s predicate.
402    ///
403    /// Holds the constructed validated value.
404    Valid(V),
405
406    /// The precursor did not satisfy `V`'s predicate.
407    ///
408    /// Holds the original precursor (returned unchanged, by move)
409    /// and the diagnostic reason.
410    Invalid(P, V::InvalidReason),
411}
412
413impl<V: Validated, P> MaybeValidOwned<V, P> {
414    /// Returns `true` if this is the `Valid` variant.
415    pub fn is_valid(&self) -> bool {
416        matches!(self, MaybeValidOwned::Valid(_))
417    }
418
419    /// Returns `true` if this is the `Invalid` variant.
420    pub fn is_invalid(&self) -> bool {
421        matches!(self, MaybeValidOwned::Invalid(_, _))
422    }
423
424    /// Returns the validated value, or `None` if invalid.
425    ///
426    /// Discards the precursor on the invalid path; use
427    /// [`invalid_parts`] to retain both the precursor and the reason.
428    ///
429    /// [`invalid_parts`]: MaybeValidOwned::invalid_parts
430    pub fn valid(self) -> Option<V> {
431        match self {
432            MaybeValidOwned::Valid(v) => Some(v),
433            MaybeValidOwned::Invalid(_, _) => None,
434        }
435    }
436
437    /// Returns the precursor on the invalid path, or `None` if valid.
438    ///
439    /// Discards the reason; use [`invalid_parts`] to retain both.
440    ///
441    /// [`invalid_parts`]: MaybeValidOwned::invalid_parts
442    pub fn invalid_precursor(self) -> Option<P> {
443        match self {
444            MaybeValidOwned::Valid(_) => None,
445            MaybeValidOwned::Invalid(p, _) => Some(p),
446        }
447    }
448
449    /// Returns the invalid reason, or `None` if valid.
450    ///
451    /// Discards the precursor; use [`invalid_parts`] to retain both.
452    ///
453    /// [`invalid_parts`]: MaybeValidOwned::invalid_parts
454    pub fn invalid_reason(self) -> Option<V::InvalidReason> {
455        match self {
456            MaybeValidOwned::Valid(_) => None,
457            MaybeValidOwned::Invalid(_, r) => Some(r),
458        }
459    }
460
461    /// Returns the precursor and reason on the invalid path, or
462    /// `None` if valid.
463    pub fn invalid_parts(self) -> Option<(P, V::InvalidReason)> {
464        match self {
465            MaybeValidOwned::Valid(_) => None,
466            MaybeValidOwned::Invalid(p, r) => Some((p, r)),
467        }
468    }
469
470    /// Returns a `MaybeValidRef` that borrows from this one, with the
471    /// same variant structure.
472    ///
473    /// Useful for inspecting an owned outcome without consuming it.
474    /// The returned value borrows `V` as `&V` and the precursor as
475    /// `&P`, and clones the `InvalidReason`.
476    pub fn as_ref(&self) -> MaybeValidRef<'_, V, P>
477    where
478        V::InvalidReason: Clone,
479    {
480        match self {
481            MaybeValidOwned::Valid(v) => MaybeValidRef::Valid(v),
482            MaybeValidOwned::Invalid(p, r) => MaybeValidRef::Invalid(p, r.clone()),
483        }
484    }
485
486    /// Converts into a `Result`, discarding the peer framing and
487    /// bundling the precursor into the error.
488    ///
489    /// Useful when integrating with code written against `Result` and
490    /// `?`, at the cost of the explicit-match ergonomics
491    /// `MaybeValidOwned` encourages.
492    pub fn into_result(self) -> Result<V, (P, V::InvalidReason)> {
493        match self {
494            MaybeValidOwned::Valid(v) => Ok(v),
495            MaybeValidOwned::Invalid(p, r) => Err((p, r)),
496        }
497    }
498
499    /// Converts into a `Result` that carries only the reason on the
500    /// error path, discarding the precursor.
501    ///
502    /// Prefer [`into_result`] when the precursor is still useful to
503    /// the caller; this method is a convenience for call sites that
504    /// only need the diagnostic.
505    ///
506    /// [`into_result`]: MaybeValidOwned::into_result
507    pub fn into_result_reason_only(self) -> Result<V, V::InvalidReason> {
508        match self {
509            MaybeValidOwned::Valid(v) => Ok(v),
510            MaybeValidOwned::Invalid(_, r) => Err(r),
511        }
512    }
513}
514
515/// Borrows `Self` as a validated view of type `V`, if `self` satisfies
516/// `V`'s predicate.
517///
518/// `AsValidated<V>` expresses that a reference to `Self` can be
519/// reinterpreted as a reference to `V` whenever `self`'s contents
520/// satisfy the validation predicate declared by `V: Validated`. The
521/// conversion is a borrow: the returned `&V` aliases memory owned by
522/// `self`, and no allocation occurs on either the valid or invalid
523/// path.
524///
525/// This is the fallible analog of [`AsRef`], restricted to
526/// *structural refinements* — cases where `V` shares a representation
527/// with `Self` and differs only in which values are permitted. For
528/// non-structural fallible conversions (parsing a string into a binary
529/// value, for example), use [`TryFrom`] or [`FromStr`].
530///
531/// [`AsRef`]: core::convert::AsRef
532/// [`TryFrom`]: core::convert::TryFrom
533/// [`FromStr`]: core::str::FromStr
534///
535/// # Contract
536///
537/// Implementers of `AsValidated<V>` promise:
538///
539/// 1. **Structural conversion.** On the valid path, the returned
540///    `&V` must alias memory within `self`. No new storage is
541///    allocated, and no owned intermediate value is constructed.
542///
543/// 2. **Cost bounded by validation.** The method performs at most the
544///    work inherent to deciding whether `self` satisfies `V`'s
545///    predicate. It does not do additional work that could be deferred
546///    or cached elsewhere.
547///
548/// 3. **Diagnostic-only reason.** The [`InvalidReason`] returned on
549///    the invalid path does not carry a copy of `self`'s contents.
550///    The precursor is returned as a reference alongside the reason,
551///    aliasing the same `&self` the caller passed in.
552///
553/// 4. **Deterministic classification.** Whether a given `self`
554///    produces `Valid` or `Invalid` depends only on `self`'s observable
555///    state. Repeated calls with the same state produce the same
556///    classification.
557///
558/// These are logical contracts, not compiler-enforced ones. Violating
559/// them does not cause undefined behavior but breaks the guarantees
560/// that generic code written against `AsValidated` relies on.
561///
562/// [`InvalidReason`]: crate::Validated::InvalidReason
563///
564/// # Examples
565///
566/// Borrowing `&[u8]` as `&str` when the bytes are valid UTF-8:
567///
568/// ```
569/// # use maybe_valid::{AsValidated, MaybeValidRef};
570/// let bytes: &[u8] = b"hello";
571/// let validated: MaybeValidRef<'_, str, [u8]> = bytes.as_validated();
572/// match validated {
573///     MaybeValidRef::Valid(s) => {
574///         assert_eq!(s, "hello");
575///     }
576///     MaybeValidRef::Invalid(bytes, reason) => {
577///         eprintln!(
578///             "invalid at byte {} of {} total",
579///             reason.valid_up_to(),
580///             bytes.len(),
581///         );
582///     }
583/// }
584/// ```
585///
586/// The `&str` returned in the valid branch aliases the original byte
587/// slice. No allocation occurred, and the bytes remain accessible
588/// through both the `bytes` binding and the `Invalid` branch's first
589/// component.
590///
591/// # Relationship to `IntoValidated`
592///
593/// `AsValidated` and [`IntoValidated`] are peers: the first borrows,
594/// the second consumes. A type that can be validated by borrow can
595/// typically also be validated by ownership. Both route through the
596/// same [`Validated`] target type and share its [`InvalidReason`].
597///
598/// Paired borrowed/owned validated types — such as [`str`] / `String`
599/// or [`CStr`] / `CString` — should share an `InvalidReason` so that
600/// `MaybeValidRef::into_owned` (when the `alloc` feature is enabled) can convert between them without
601/// requiring a reason-type conversion.
602///
603/// [`IntoValidated`]: crate::IntoValidated
604/// [`Validated`]: crate::Validated
605/// [`str`]: prim@str
606/// [`CStr`]: core::ffi::CStr
607pub trait AsValidated<V: Validated + ?Sized> {
608    /// Borrows `self` as a validated `V`, if valid.
609    ///
610    /// Returns `MaybeValidRef::Valid(&v)` when `self` satisfies `V`'s
611    /// predicate, with `v` aliasing memory in `self`. Returns
612    /// `MaybeValidRef::Invalid(&self, reason)` otherwise, with the
613    /// precursor reference and the diagnostic reason.
614    fn as_validated(&self) -> MaybeValidRef<'_, V, Self>;
615}
616
617/// Consumes `Self` into a validated value of type `V`, if `self`
618/// satisfies `V`'s predicate.
619///
620/// `IntoValidated<V>` expresses that `Self` can be converted into `V`
621/// whenever `self`'s contents satisfy the validation predicate declared
622/// by `V: Validated`. On failure, `self` is returned unchanged
623/// alongside the diagnostic, so the caller does not lose their input.
624///
625/// This is the owning counterpart to [`AsValidated`]. Use it when the
626/// validated form is an owned value (for example, converting
627/// `Vec<u8>` into `String`) rather than a borrowed view.
628///
629/// [`AsValidated`]: crate::AsValidated
630///
631/// # Contract
632///
633/// Implementers of `IntoValidated<V>` promise:
634///
635/// 1. **Precursor recovery on failure.** When validation fails, the
636///    returned [`MaybeValidOwned::Invalid`] variant contains `self`
637///    unchanged. The caller can always recover their input.
638///
639/// 2. **No precursor cloning.** Recovery on the invalid path returns
640///    the original `self` by move, not a clone. The trait does not
641///    require `Self: Clone`.
642///
643/// 3. **Cost bounded by validation and construction.** The method
644///    performs at most the work inherent to deciding whether `self`
645///    satisfies `V`'s predicate and constructing the resulting `V`.
646///    Unlike [`AsValidated`], construction may involve allocation or
647///    transformation when producing the owned `V` requires it.
648///
649/// 4. **Diagnostic-only reason.** The [`InvalidReason`] component of
650///    the invalid branch does not carry a copy of `self`; the
651///    precursor is returned structurally via the tuple.
652///
653/// 5. **Deterministic classification.** Whether a given `self`
654///    produces `Valid` or `Invalid` depends only on `self`'s observable
655///    state.
656///
657/// These are logical contracts, not compiler-enforced ones.
658///
659/// [`InvalidReason`]: crate::Validated::InvalidReason
660/// [`MaybeValidOwned::Invalid`]: crate::MaybeValidOwned::Invalid
661///
662/// # Examples
663///
664/// Consuming `Vec<u8>` into `String`, recovering the bytes on failure
665/// without a clone:
666///
667/// ```
668/// # #[cfg(feature = "alloc")]
669/// # {
670/// # use maybe_valid::{IntoValidated, MaybeValidOwned};
671/// let bytes = vec![0xff, 0xfe, 0xfd];
672/// let validated: MaybeValidOwned<String, Vec<u8>> = bytes.into_validated();
673/// match validated {
674///     MaybeValidOwned::Valid(s) => {
675///         println!("got string: {}", s);
676///     }
677///     MaybeValidOwned::Invalid(bytes, reason) => {
678///         // `bytes` is the original Vec<u8>, moved back to us.
679///         // No clone occurred on the failure path.
680///         eprintln!(
681///             "invalid UTF-8 at byte {}; recovered {} bytes",
682///             reason.valid_up_to(),
683///             bytes.len(),
684///         );
685///     }
686/// }
687/// # }
688/// ```
689///
690/// Converting `u32` into `NonZeroU32`:
691///
692/// ```
693/// # use maybe_valid::{IntoValidated, MaybeValidOwned};
694/// # use std::num::NonZeroU32;
695/// let candidates = [1u32, 0, 42];
696/// for n in candidates {
697///     let validated: MaybeValidOwned<NonZeroU32, u32> = n.into_validated();
698///     match validated {
699///         MaybeValidOwned::Valid(nz) => println!("{} is nonzero", nz),
700///         MaybeValidOwned::Invalid(original, _) => {
701///             assert_eq!(original, 0);
702///             println!("zero, skipping");
703///         }
704///     }
705/// }
706/// ```
707///
708/// # Relationship to `AsValidated`
709///
710/// `IntoValidated` and [`AsValidated`] are peers: the second borrows,
711/// the first consumes. Both route through the same [`Validated`]
712/// target type and share its [`InvalidReason`]. Paired borrowed/owned
713/// validated types should share an `InvalidReason` so that outcomes
714/// can round-trip between the two via
715/// `MaybeValidRef::into_owned` (when the `alloc` feature is enabled).
716///
717/// [`Validated`]: crate::Validated
718///
719/// # Relationship to `TryFrom`
720///
721/// `IntoValidated<V>` overlaps in shape with [`TryFrom<Self>`] for
722/// `V`, but differs in three ways:
723///
724/// - **Canonical reason type.** `IntoValidated<V>` routes all failures
725///   through `V::InvalidReason`, which is fixed by the target type.
726///   `TryFrom` lets each impl choose its own error.
727///
728/// - **Structural precursor recovery.** `IntoValidated` guarantees via
729///   its signature that `self` is returned on failure.
730///   `TryFrom::Error` may or may not carry the precursor, depending on
731///   the impl; callers cannot rely on recovery in generic code.
732///
733/// - **Scope.** `IntoValidated` is intended for *structural
734///   refinements*, where `V` is a subset of `Self`'s representation
735///   with a validation predicate. Use `TryFrom` for conversions that
736///   change representation (parsing, decoding, reinterpretation).
737///
738/// These differences make `IntoValidated` a better fit for generic
739/// code that needs to rely on precursor recovery or uniform error
740/// handling, and `TryFrom` a better fit for general fallible
741/// conversion.
742///
743/// [`TryFrom<Self>`]: core::convert::TryFrom
744///
745/// # Relationship to `FromStr`
746///
747/// [`FromStr`] parses a string into a value, which is typically a
748/// representation-changing operation. `IntoValidated` is for
749/// structural refinements and does not apply to parsing. Types like
750/// [`IpAddr`] or [`Duration`], which are constructed from strings but
751/// have internal binary representations unrelated to the string's
752/// bytes, use `FromStr`, not `IntoValidated`.
753///
754/// [`FromStr`]: core::str::FromStr
755/// [`IpAddr`]: core::net::IpAddr
756/// [`Duration`]: core::time::Duration
757pub trait IntoValidated<V: Validated>: Sized {
758    /// Consumes `self` into a validated `V`, if valid.
759    ///
760    /// Returns `MaybeValidOwned::Valid(v)` when `self` satisfies `V`'s
761    /// predicate. Returns `MaybeValidOwned::Invalid(self, reason)`
762    /// otherwise, with `self` returned unchanged.
763    fn into_validated(self) -> MaybeValidOwned<V, Self>;
764}