use indexmap::{IndexMap, IndexSet};
use crate::error::SchemaError;
use crate::schema::{FieldType, Record, Ref, Schema};
use super::minimize::normalize;
use super::prune::prune;
pub fn extract(s: &Schema, keep: &[&str]) -> Result<Schema, SchemaError> {
let keep_set: IndexSet<String> = keep.iter().map(|s| (*s).to_string()).collect();
let mut trimmed: IndexMap<String, Record> = IndexMap::new();
let mut invalidated: IndexSet<String> = IndexSet::new();
let mut first_offender: Option<(String, String)> = None;
for (name, rec) in s.env() {
let mut kept_fields = Vec::new();
for f in rec.fields() {
if keep_set.contains(&f.label) {
kept_fields.push(f.clone());
} else if f.min >= 1 {
if first_offender.is_none() {
first_offender = Some((f.label.clone(), name.clone()));
}
invalidated.insert(name.clone());
}
}
trimmed.insert(
name.clone(),
Record::new(kept_fields).expect(
"dropping fields from an already-valid Record cannot introduce a duplicate label",
),
);
}
let mut changed = true;
while changed {
changed = false;
for (name, rec) in &trimmed {
if invalidated.contains(name) {
continue;
}
for f in rec.fields() {
if f.min >= 1
&& let FieldType::Ref(r) = &f.ty
&& invalidated.contains(&r.name)
{
invalidated.insert(name.clone());
changed = true;
break;
}
}
}
}
if invalidated.contains(&s.root().name) {
let (label, record_name) = first_offender.expect(
"root invalidated implies step 1 recorded an offender before propagation began",
);
return Err(SchemaError::new(
format!("{record_name}.{label}"),
"algebra.extract-invalidates-root",
format!(
"no valid subschema: removing label {label:?} deletes a mandatory field of record {record_name:?}"
),
));
}
let mut new_env: IndexMap<String, Record> = IndexMap::new();
for (name, rec) in &trimmed {
if invalidated.contains(name) {
continue;
}
let fields: Vec<_> = rec
.fields()
.iter()
.filter(|f| !matches!(&f.ty, FieldType::Ref(r) if invalidated.contains(&r.name)))
.cloned()
.collect();
new_env.insert(
name.clone(),
Record::new(fields).expect(
"dropping fields typed to an already-invalidated record cannot introduce a duplicate label",
),
);
}
let result = Schema::new(Ref::new(s.root().name.clone()), new_env).expect(
"dropping only invalidated records/fields leaves every surviving Ref resolvable within \
the surviving env",
);
Ok(normalize(&prune(&result)))
}