Solver

Struct Solver 

Source
pub struct Solver<'a> { /* private fields */ }
Expand description

State machine solver for lazy value-based disambiguation.

This solver only requests value inspection when candidates disagree on type. For keys where all candidates expect the same type, the deserializer can skip detailed value analysis.

§Example

use facet::Facet;
use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};

#[derive(Facet)]
#[repr(u8)]
enum NumericValue {
    Small(u8),
    Large(u16),
}

#[derive(Facet)]
struct Container {
    #[facet(flatten)]
    value: NumericValue,
}

let schema = Schema::build(Container::SHAPE).unwrap();
let mut solver = Solver::new(&schema);

// The field "0" has different types (u8 vs u16) - solver needs disambiguation
match solver.see_key("0") {
    KeyResult::Ambiguous { fields } => {
        // Deserializer sees value "1000", checks which fields can accept it
        // u8 can't hold 1000, u16 can - so only report the u16 field
        // Fields come with specificity scores - lower = more specific
        let satisfied: Vec<_> = fields.iter()
            .filter(|(f, _score)| {
                // deserializer's logic: can this value parse as this field's type?
                f.value_shape.type_identifier == "u16"
            })
            .map(|(f, _)| *f)
            .collect();

        match solver.satisfy(&satisfied) {
            SatisfyResult::Solved(config) => {
                assert!(config.describe().contains("Large"));
            }
            _ => panic!("expected solved"),
        }
    }
    _ => panic!("expected Ambiguous"),
}

Implementations§

Source§

impl<'a> Solver<'a>

Source

pub fn new(schema: &'a Schema) -> Self

Create a new solver from a schema.

Source

pub fn see_key(&mut self, key: &'a str) -> KeyResult<'a>

Report a key. Returns what to do next.

  • Unambiguous: All candidates agree on the type - parse directly
  • Ambiguous: Types differ - check which fields the value can satisfy
  • Solved: Disambiguated to one config
  • Unknown: Key not found in any candidate
Source

pub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a>

Report which fields the value can satisfy after Ambiguous result.

The deserializer should pass the subset of fields (from the Ambiguous result) that the actual value can be parsed into.

Source

pub fn satisfy_shapes( &mut self, satisfied_shapes: &[&'static Shape], ) -> SatisfyResult<'a>

Report which shapes the value can satisfy after Ambiguous result from probe_key.

This is the shape-based version of satisfy, used when disambiguating by nested field types. The deserializer should pass the shapes that the actual value can be parsed into.

§Example
use facet::Facet;
use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};

#[derive(Facet)]
struct SmallPayload { value: u8 }

#[derive(Facet)]
struct LargePayload { value: u16 }

#[derive(Facet)]
#[repr(u8)]
enum PayloadKind {
    Small { payload: SmallPayload },
    Large { payload: LargePayload },
}

#[derive(Facet)]
struct Container {
    #[facet(flatten)]
    inner: PayloadKind,
}

let schema = Schema::build(Container::SHAPE).unwrap();
let mut solver = Solver::new(&schema);

// Report nested key
solver.probe_key(&[], "payload");

// At payload.value, value is 1000 - doesn't fit u8
// Get shapes at this path
let shapes = solver.get_shapes_at_path(&["payload", "value"]);
// Filter to shapes that can hold 1000
let works: Vec<_> = shapes.iter()
    .filter(|s| s.type_identifier == "u16")
    .copied()
    .collect();
solver.satisfy_shapes(&works);
Source

pub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape>

Get the shapes at a nested path across all remaining candidates.

This is useful when you have an Ambiguous result from probe_key and need to know what types are possible at that path.

Source

pub fn satisfy_at_path( &mut self, path: &[&str], satisfied_shapes: &[&'static Shape], ) -> SatisfyResult<'a>

Report which shapes at a nested path the value can satisfy.

This is the path-aware version of satisfy_shapes, used when disambiguating by nested field types after probe_key.

  • path: The full path to the field (e.g., ["payload", "value"])
  • satisfied_shapes: The shapes that the value can be parsed into
Source

pub fn candidates(&self) -> Vec<&'a Configuration>

Get the current candidate configurations.

Source

pub fn seen_keys(&self) -> &BTreeSet<&'a str>

Get the seen keys.

Source

pub fn hint_variant(&mut self, variant_name: &str) -> bool

Hint that a specific enum variant should be selected.

This filters the candidates to only those configurations where at least one variant selection has the given variant name. This is useful for explicit type disambiguation via annotations (e.g., KDL type annotations like (Http)node).

Returns true if at least one candidate remains after filtering, false if no candidates match the variant name (in which case candidates are unchanged).

§Example
use facet::Facet;
use facet_solver::{Schema, Solver};

#[derive(Facet)]
struct HttpSource { url: String }

#[derive(Facet)]
struct GitSource { url: String, branch: String }

#[derive(Facet)]
#[repr(u8)]
enum SourceKind {
    Http(HttpSource),
    Git(GitSource),
}

#[derive(Facet)]
struct Source {
    #[facet(flatten)]
    kind: SourceKind,
}

let schema = Schema::build(Source::SHAPE).unwrap();
let mut solver = Solver::new(&schema);

// Without hint, both variants are candidates
assert_eq!(solver.candidates().len(), 2);

// Hint at Http variant
assert!(solver.hint_variant("Http"));
assert_eq!(solver.candidates().len(), 1);
Source

pub fn mark_seen(&mut self, key: &'a str)

Mark a key as seen without filtering candidates.

This is useful when the key is known to be present through means other than parsing (e.g., type annotations). Call this after hint_variant to mark the variant name as seen so that finish() doesn’t report it as missing.

Source

pub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a>

Report a key at a nested path. Returns what to do next.

This is the depth-aware version of see_key. Use this when probing nested structures where disambiguation might require looking inside objects.

  • path: The ancestor keys (e.g., ["payload"] when inside a payload object)
  • key: The key found at this level (e.g., "value")
§Example
use facet::Facet;
use facet_solver::{Schema, Solver, KeyResult};

#[derive(Facet)]
struct SmallPayload { value: u8 }

#[derive(Facet)]
struct LargePayload { value: u16 }

#[derive(Facet)]
#[repr(u8)]
enum PayloadKind {
    Small { payload: SmallPayload },
    Large { payload: LargePayload },
}

#[derive(Facet)]
struct Container {
    #[facet(flatten)]
    inner: PayloadKind,
}

let schema = Schema::build(Container::SHAPE).unwrap();
let mut solver = Solver::new(&schema);

// "payload" exists in both - keep going
solver.probe_key(&[], "payload");

// "value" inside payload - both have it but different types!
match solver.probe_key(&["payload"], "value") {
    KeyResult::Ambiguous { fields } => {
        // fields is Vec<(&FieldInfo, u64)> - field + specificity score
        // Deserializer checks: 1000 fits u16 but not u8
        // When multiple match, pick the one with lowest score (most specific)
    }
    _ => {}
}
Source

pub fn finish(self) -> Result<&'a Configuration, SolverError>

Finish solving. Call this after all keys have been processed.

This method is necessary because key-based filtering alone cannot disambiguate when one variant’s required fields are a subset of another’s.

§Why not just use see_key() results?

see_key() returns Solved when a key excludes candidates down to one. But when the input is a valid subset of multiple variants, no key excludes anything — you need finish() to check which candidates have all their required fields satisfied.

§Example
enum Source {
    Http { url: String },                  // required: url
    Git { url: String, branch: String },   // required: url, branch
}
Inputsee_key behaviorResolution
{ "url", "branch" }branch excludes Http → candidates = 1Early Solved(Git)
{ "url" }both have url → candidates = 2finish()Http

In the second case, no key ever excludes a candidate. Only finish() can determine that Git is missing its required branch field, leaving Http as the sole viable configuration.

Trait Implementations§

Source§

impl<'a> Debug for Solver<'a>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a> Freeze for Solver<'a>

§

impl<'a> RefUnwindSafe for Solver<'a>

§

impl<'a> Send for Solver<'a>

§

impl<'a> Sync for Solver<'a>

§

impl<'a> Unpin for Solver<'a>

§

impl<'a> UnwindSafe for Solver<'a>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.