onepassword-sys 0.1.0

Mostly raw bindings to the 1Password SDK
use byteorder::{BigEndian, ByteOrder};

use crate::buffer::RustBuffer;

pub type FfiResult<T = (), E = Error> = Result<T, E>;

#[derive(Debug)]
pub enum Error {
    Error { code: i32, message: String },
}

impl Error {
    pub fn code(&self) -> i32 {
        match self {
            Self::Error { code, .. } => *code,
        }
    }
}

#[repr(u8)]
#[expect(dead_code)]
pub(crate) enum CallStatusCode {
    Success = 0,
    Error = 1,
    Panic = 2,
}

#[repr(C)]
pub(crate) struct CallStatus {
    pub code: CallStatusCode,
    pub error_buf: RustBuffer,
}

pub(crate) trait ErrorConverter {
    type ErrorType;

    fn lift(buf: RustBuffer) -> Self::ErrorType;
}

pub(crate) struct NoConverter;
impl ErrorConverter for NoConverter {
    type ErrorType = std::convert::Infallible;

    fn lift(_buf: RustBuffer) -> Self::ErrorType {
        panic!("_rust_call_with_error: CALL_ERROR, but error_ffi_converter is None");
    }
}

pub(crate) struct StringConverter;
impl ErrorConverter for StringConverter {
    type ErrorType = String;

    fn lift(buf: RustBuffer) -> Self::ErrorType {
        buf.to_string()
    }
}

pub(crate) struct ErrorTypeConverter;
impl ErrorConverter for ErrorTypeConverter {
    type ErrorType = Error;

    fn lift(buf: RustBuffer) -> Self::ErrorType {
        let error_variant = BigEndian::read_i32(buf.as_ref());
        let error_code = BigEndian::read_i32(&buf.as_ref()[4..]);

        match error_variant {
            1 => {
                let remainder = &buf.as_ref()[8..];
                let msg = String::from_utf8_lossy(remainder);
                Error::Error {
                    code: error_code,
                    message: msg.into_owned(),
                }
            }
            other => unimplemented!("unknown error variant {other}"),
        }
    }
}

pub(crate) fn check_call_status<C: ErrorConverter>(
    call_status: CallStatus,
) -> Result<(), C::ErrorType> {
    match call_status.code {
        CallStatusCode::Success => Ok(()),
        CallStatusCode::Error => Err(C::lift(call_status.error_buf)),
        CallStatusCode::Panic => {
            let msg = if call_status.error_buf.len > 0 {
                StringConverter::lift(call_status.error_buf)
            } else {
                "Unknown rust panic".to_string()
            };
            panic!("{msg}");
        }
    }
}