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>
impl<'a> Solver<'a>
Sourcepub fn see_key(&mut self, key: impl Into<Cow<'a, str>>) -> KeyResult<'a>
pub fn see_key(&mut self, key: impl Into<Cow<'a, str>>) -> KeyResult<'a>
Report a key. Returns what to do next.
Unambiguous: All candidates agree on the type - parse directlyAmbiguous: Types differ - check which fields the value can satisfySolved: Disambiguated to one configUnknown: Key not found in any candidate
Accepts both borrowed (&str) and owned (String) keys via Cow.
Sourcepub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a>
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.
Sourcepub fn satisfy_shapes(
&mut self,
satisfied_shapes: &[&'static Shape],
) -> SatisfyResult<'a>
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);Sourcepub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape>
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.
Sourcepub fn satisfy_at_path(
&mut self,
path: &[&str],
satisfied_shapes: &[&'static Shape],
) -> SatisfyResult<'a>
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
Sourcepub fn candidates(&self) -> Vec<&'a Resolution>
pub fn candidates(&self) -> Vec<&'a Resolution>
Get the current candidate resolutions.
Sourcepub fn hint_variant(&mut self, variant_name: &str) -> bool
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 resolutions 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);Sourcepub fn mark_seen(&mut self, key: impl Into<Cow<'a, str>>)
pub fn mark_seen(&mut self, key: impl Into<Cow<'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.
Sourcepub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a>
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)
}
_ => {}
}Sourcepub fn finish(self) -> Result<&'a Resolution, SolverError>
pub fn finish(self) -> Result<&'a Resolution, 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
}| Input | see_key behavior | Resolution |
|---|---|---|
{ "url", "branch" } | branch excludes Http → candidates = 1 | Early Solved(Git) |
{ "url" } | both have url → candidates = 2 | finish() → 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.