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}