use briefcase_core::{PiiType, Redaction, SanitizationJsonResult, SanitizationResult, Sanitizer};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
#[pyclass(name = "Sanitizer")]
pub struct PySanitizer {
pub inner: Sanitizer,
}
#[pymethods]
impl PySanitizer {
#[new]
fn new() -> Self {
Self {
inner: Sanitizer::new(),
}
}
#[staticmethod]
fn disabled() -> Self {
Self {
inner: Sanitizer::disabled(),
}
}
fn sanitize(&self, text: String) -> PySanitizationResult {
let result = self.inner.sanitize(&text);
PySanitizationResult { inner: result }
}
fn sanitize_json(&self, data: PyObject) -> PyResult<PyJsonSanitizationResult> {
Python::with_gil(|py| {
let json_value = crate::models::python_to_json_value(data, py)?;
let result = self.inner.sanitize_json(&json_value);
Ok(PyJsonSanitizationResult { inner: result })
})
}
fn contains_pii(&self, text: String) -> bool {
let pii_matches = self.inner.contains_pii(&text);
!pii_matches.is_empty()
}
fn analyze_pii(&self, text: String) -> PyResult<PyObject> {
let analysis = self.inner.analyze(&text);
Python::with_gil(|py| {
let dict = PyDict::new(py);
dict.set_item("has_pii", analysis.has_pii)?;
dict.set_item("total_matches", analysis.total_matches)?;
dict.set_item("unique_types", analysis.unique_types)?;
let types_list = PyList::empty(py);
for pii_type in analysis.type_counts.keys() {
let type_str = match pii_type {
PiiType::Ssn => "ssn",
PiiType::CreditCard => "credit_card",
PiiType::Email => "email",
PiiType::Phone => "phone",
PiiType::ApiKey => "api_key",
PiiType::IpAddress => "ip_address",
PiiType::Custom(name) => name,
};
types_list.append(type_str)?;
}
dict.set_item("detected_types", types_list)?;
Ok(dict.into())
})
}
fn add_pattern(&mut self, name: String, pattern: String) -> PyResult<()> {
self.inner
.add_pattern(&name, &pattern)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(e.to_string()))
}
fn remove_pattern(&mut self, pattern_name: String) -> bool {
let pii_type = PiiType::Custom(pattern_name);
self.inner.remove_pattern(&pii_type)
}
fn set_enabled(&mut self, enabled: bool) {
self.inner.set_enabled(enabled);
}
#[getter]
fn enabled(&self) -> bool {
!self
.inner
.sanitize("test@example.com")
.sanitized
.contains("test@example.com")
}
fn __repr__(&self) -> String {
"Sanitizer()".to_string()
}
}
#[pyclass(name = "SanitizationResult")]
pub struct PySanitizationResult {
pub inner: SanitizationResult,
}
#[pyclass(name = "SanitizationJsonResult")]
pub struct PyJsonSanitizationResult {
pub inner: SanitizationJsonResult,
}
#[pymethods]
impl PySanitizationResult {
#[getter]
fn sanitized(&self) -> String {
self.inner.sanitized.clone()
}
#[getter]
fn redactions(&self) -> PyResult<PyObject> {
Python::with_gil(|py| {
let list = PyList::empty(py);
for redaction in &self.inner.redactions {
let py_redaction = PyRedaction {
inner: redaction.clone(),
};
list.append(Py::new(py, py_redaction)?)?;
}
Ok(list.into())
})
}
#[getter]
fn redaction_count(&self) -> usize {
self.inner.redactions.len()
}
#[getter]
fn has_redactions(&self) -> bool {
!self.inner.redactions.is_empty()
}
fn to_dict(&self) -> PyResult<PyObject> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
dict.set_item("sanitized", self.sanitized())?;
dict.set_item("redaction_count", self.redaction_count())?;
dict.set_item("has_redactions", self.has_redactions())?;
let redactions_list = PyList::empty(py);
for redaction in &self.inner.redactions {
let redaction_dict = PyDict::new(py);
redaction_dict.set_item(
"pii_type",
format!("{:?}", redaction.pii_type).to_lowercase(),
)?;
redaction_dict.set_item("start_position", redaction.start_position)?;
redaction_dict.set_item("end_position", redaction.end_position)?;
redaction_dict.set_item("original_length", redaction.original_length)?;
redactions_list.append(redaction_dict)?;
}
dict.set_item("redactions", redactions_list)?;
Ok(dict.into())
})
}
fn __repr__(&self) -> String {
format!(
"SanitizationResult(redactions={})",
self.inner.redactions.len()
)
}
}
#[pymethods]
impl PyJsonSanitizationResult {
#[getter]
fn sanitized(&self) -> PyObject {
Python::with_gil(|py| {
crate::models::json_value_to_python(&self.inner.sanitized, py)
.unwrap_or_else(|_| py.None())
})
}
#[getter]
fn redaction_count(&self) -> usize {
self.inner.redactions.len()
}
fn to_dict(&self) -> PyResult<PyObject> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
dict.set_item("sanitized", self.sanitized())?;
dict.set_item("redaction_count", self.redaction_count())?;
Ok(dict.into())
})
}
fn __repr__(&self) -> String {
format!(
"SanitizationJsonResult(redactions={})",
self.inner.redactions.len()
)
}
}
#[pyclass(name = "Redaction")]
pub struct PyRedaction {
pub inner: Redaction,
}
#[pymethods]
impl PyRedaction {
#[getter]
fn pii_type(&self) -> String {
match &self.inner.pii_type {
PiiType::Ssn => "ssn".to_string(),
PiiType::CreditCard => "credit_card".to_string(),
PiiType::Email => "email".to_string(),
PiiType::Phone => "phone".to_string(),
PiiType::ApiKey => "api_key".to_string(),
PiiType::IpAddress => "ip_address".to_string(),
PiiType::Custom(name) => name.clone(),
}
}
#[getter]
fn start_position(&self) -> usize {
self.inner.start_position
}
#[getter]
fn end_position(&self) -> usize {
self.inner.end_position
}
#[getter]
fn original_length(&self) -> usize {
self.inner.original_length
}
fn to_dict(&self) -> PyResult<PyObject> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
dict.set_item("pii_type", self.pii_type())?;
dict.set_item("start_position", self.inner.start_position)?;
dict.set_item("end_position", self.inner.end_position)?;
dict.set_item("original_length", self.inner.original_length)?;
Ok(dict.into())
})
}
fn __repr__(&self) -> String {
format!(
"Redaction(type='{}', position={}:{})",
self.pii_type(),
self.inner.start_position,
self.inner.end_position
)
}
}