synta-python 0.3.1

Python extension module for the synta ASN.1 library
Documentation
//! Python bindings for the RFC 7773 Authentication Context certificate
//! extension (ACE-88) builder.
//!
//! Exposes [`PyAuthenticationContextsBuilder`] for constructing DER-encoded
//! ``AuthenticationContexts`` structures (OID ``1.2.752.201.5.1``).

use pyo3::prelude::*;
use pyo3::types::PyBytes;

// ── PyAuthenticationContextsBuilder ──────────────────────────────────────────

/// Python-facing wrapper for
/// [`synta_certificate::AuthenticationContextsBuilder`].
///
/// Builds a DER-encoded ``AuthenticationContexts`` SEQUENCE OF (RFC 7773 /
/// ACE-88).
///
/// Each entry is an ``AuthenticationContext`` with a mandatory
/// ``contextType`` UTF8String and an optional ``contextInfo`` UTF8String.
/// At least one entry must be added before calling :meth:`build`.
///
/// The resulting DER goes inside the OCTET STRING wrapper of an X.509
/// ``Extension`` SEQUENCE for OID ``1.2.752.201.5.1``
/// (``id-ce-authContext``).
///
/// Example::
///
///     import synta
///
///     extn_der = (
///         synta.AuthenticationContextsBuilder()
///         .add("urn:id:skatteverket:2:1.0", None)
///         .add(
///             "urn:id:skatteverket:1:1.0",
///             "https://www.skatteverket.se/ac/context",
///         )
///         .build()
///     )
#[pyclass(name = "AuthenticationContextsBuilder")]
pub struct PyAuthenticationContextsBuilder {
    inner: synta_certificate::AuthenticationContextsBuilder,
    built: bool,
}

#[pymethods]
impl PyAuthenticationContextsBuilder {
    /// Create a new, empty ``AuthenticationContextsBuilder``.
    #[new]
    fn new() -> Self {
        Self {
            inner: synta_certificate::AuthenticationContextsBuilder::new(),
            built: false,
        }
    }

    /// Add an ``AuthenticationContext`` entry.
    ///
    /// :param context_type: URI string identifying the authentication context,
    ///     e.g. ``"urn:id:skatteverket:2:1.0"``.
    /// :param context_info: optional URL pointing to a document describing the
    ///     context (e.g. ``"https://www.skatteverket.se/ac/context.xml"``), or
    ///     ``None``.
    /// :raises ValueError: if DER encoding fails (deferred to :meth:`build`).
    #[pyo3(signature = (context_type, context_info=None))]
    fn add<'py>(
        slf: Bound<'py, Self>,
        context_type: &str,
        context_info: Option<&str>,
    ) -> Bound<'py, Self> {
        {
            let mut guard = slf.borrow_mut();
            let old = std::mem::replace(
                &mut guard.inner,
                synta_certificate::AuthenticationContextsBuilder::new(),
            );
            guard.inner = old.add(context_type, context_info);
        }
        slf
    }

    /// Build the DER-encoded ``AuthenticationContexts`` SEQUENCE OF.
    ///
    /// :returns: DER bytes of the ``AuthenticationContexts``.
    /// :raises ValueError: if no entries were added or DER encoding fails.
    fn build<'py>(&mut self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
        if self.built {
            return Err(pyo3::exceptions::PyValueError::new_err(
                "build() has already been called; create a new builder",
            ));
        }
        self.built = true;
        let inner = std::mem::replace(
            &mut self.inner,
            synta_certificate::AuthenticationContextsBuilder::new(),
        );
        let der = inner
            .build()
            .map_err(pyo3::exceptions::PyValueError::new_err)?;
        Ok(PyBytes::new(py, &der))
    }

    fn __repr__(&self) -> String {
        "AuthenticationContextsBuilder()".to_string()
    }
}

// ── register ──────────────────────────────────────────────────────────────────

/// Register ACE-88 builder classes into the given module.
pub(super) fn register_ace88_classes(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<PyAuthenticationContextsBuilder>()?;
    Ok(())
}