Skip to main content

argumentation_values/
scheme_bridge.rs

1//! Bridge between `argumentation-schemes` and `ValueAssignment`.
2//!
3//! The `argument_from_values` Walton scheme (Walton 2008 p.321) carries
4//! a `value` premise slot — see
5//! `argumentation-schemes/src/catalog/practical.rs:120`. This module
6//! extracts that slot from instantiated schemes and builds a
7//! [`ValueAssignment`] keyed by the scheme's conclusion.
8//!
9//! For schemes other than `argument_from_values`, no value is extracted.
10//!
11//! ## Note on the scheme identifier
12//!
13//! `SchemeInstance` carries `scheme_name: String` (the human-readable name
14//! from `SchemeSpec::name`), not the snake-case key. We compare against the
15//! literal name `"Argument from Values"` since that is what
16//! `argument_from_values()` registers in the default catalog. If consumers
17//! register a custom values scheme under a different name, they should
18//! call [`from_scheme_instances_with_name`] with the appropriate name.
19
20use crate::types::{Value, ValueAssignment};
21use argumentation_schemes::SchemeInstance;
22use std::collections::HashMap;
23use std::hash::Hash;
24
25/// The default-catalog name of the values scheme — see
26/// `argumentation-schemes/src/catalog/practical.rs:124`.
27pub const DEFAULT_VALUES_SCHEME_NAME: &str = "Argument from Values";
28
29/// Extract value promotions from an iterator of instantiated schemes
30/// using the default catalog's values-scheme name.
31///
32/// For each scheme instance:
33/// - If `instance.scheme_name == "Argument from Values"` and a `"value"`
34///   binding is present, the scheme's conclusion is mapped to the bound
35///   value.
36/// - Otherwise, the scheme is skipped silently.
37///
38/// `to_arg` converts a `SchemeInstance` to the caller's argument label
39/// type — typically by reading the conclusion literal. For encounter use
40/// this is a closure producing an `ArgumentId` from `instance.conclusion`.
41///
42/// Bindings are passed in separately because `SchemeInstance` does not
43/// retain its bindings post-instantiation — only the resolved literals.
44/// Callers must keep the original bindings alongside each instance.
45pub fn from_scheme_instances<'a, A, I, F>(
46    instances: I,
47    to_arg: F,
48) -> ValueAssignment<A>
49where
50    A: Eq + Hash + Clone,
51    I: IntoIterator<Item = (&'a SchemeInstance, &'a HashMap<String, String>)>,
52    F: Fn(&SchemeInstance) -> A,
53{
54    from_scheme_instances_with_name(instances, to_arg, DEFAULT_VALUES_SCHEME_NAME)
55}
56
57/// Same as [`from_scheme_instances`] but lets the caller specify a custom
58/// values-scheme name (for consumers who register their own variant).
59pub fn from_scheme_instances_with_name<'a, A, I, F>(
60    instances: I,
61    to_arg: F,
62    target_scheme_name: &str,
63) -> ValueAssignment<A>
64where
65    A: Eq + Hash + Clone,
66    I: IntoIterator<Item = (&'a SchemeInstance, &'a HashMap<String, String>)>,
67    F: Fn(&SchemeInstance) -> A,
68{
69    let mut assignment = ValueAssignment::new();
70    for (instance, bindings) in instances {
71        if instance.scheme_name.as_str() != target_scheme_name {
72            continue;
73        }
74        let Some(value_str) = bindings.get("value") else {
75            continue;
76        };
77        let arg = to_arg(instance);
78        assignment.promote(arg, Value::new(value_str.clone()));
79    }
80    assignment
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use argumentation_schemes::catalog::default_catalog;
87
88    #[test]
89    fn extracts_value_from_argument_from_values_scheme() {
90        let registry = default_catalog();
91        let scheme = registry.by_key("argument_from_values").unwrap();
92
93        let mut bindings = HashMap::new();
94        bindings.insert("action".into(), "uphold_honor".into());
95        bindings.insert("value".into(), "honor".into());
96        bindings.insert("agent".into(), "alice".into());
97        let instance = scheme.instantiate(&bindings).unwrap();
98
99        let to_arg = |inst: &SchemeInstance| inst.conclusion.to_string();
100        let assignment = from_scheme_instances(
101            std::iter::once((&instance, &bindings)),
102            to_arg,
103        );
104        let arg = instance.conclusion.to_string();
105        let values = assignment.values(&arg);
106        assert_eq!(values.len(), 1);
107        assert_eq!(values[0].as_str(), "honor");
108    }
109
110    #[test]
111    fn skips_non_value_schemes() {
112        let registry = default_catalog();
113        let scheme = registry
114            .by_key("argument_from_expert_opinion")
115            .expect("argument_from_expert_opinion in default catalog");
116
117        let mut bindings = HashMap::new();
118        bindings.insert("expert".into(), "alice".into());
119        bindings.insert("domain".into(), "military".into());
120        bindings.insert("claim".into(), "fortify".into());
121        let instance = scheme.instantiate(&bindings).unwrap();
122
123        let to_arg = |inst: &SchemeInstance| inst.conclusion.to_string();
124        let assignment = from_scheme_instances(
125            std::iter::once((&instance, &bindings)),
126            to_arg,
127        );
128        // Empty assignment because the scheme is not Argument from Values.
129        assert!(assignment.values(&instance.conclusion.to_string()).is_empty());
130    }
131
132    #[test]
133    fn custom_scheme_name_supported() {
134        // If a consumer registers their own values scheme under a different
135        // name (e.g., "Custom Values"), the with_name variant lets them
136        // target it explicitly. Here we just verify the API shape compiles
137        // and behaves correctly when the name doesn't match.
138        let registry = default_catalog();
139        let scheme = registry.by_key("argument_from_values").unwrap();
140        let mut bindings = HashMap::new();
141        bindings.insert("action".into(), "do_x".into());
142        bindings.insert("value".into(), "honor".into());
143        bindings.insert("agent".into(), "alice".into());
144        let instance = scheme.instantiate(&bindings).unwrap();
145
146        let to_arg = |inst: &SchemeInstance| inst.conclusion.to_string();
147        let assignment = from_scheme_instances_with_name(
148            std::iter::once((&instance, &bindings)),
149            to_arg,
150            "Some Other Scheme Name",
151        );
152        // No match — empty assignment.
153        assert!(assignment.values(&instance.conclusion.to_string()).is_empty());
154    }
155}