//! Gate functionality to control implicitly opened dependencies.

use super::builder::FormatDriverBuilder;
use crate::{FormatAccess, Storage, StorageOpenOptions};
use std::io;

/// Gate implicit image and storage object dependencies.
///
/// Depending on their format, images may have external image and storage object dependencies; for
/// example, qcow2 images can reference a backing image (any image format), and an external data
/// file (pure storage object, no format).  You can override these implicit choices when opening
/// images, but if you do not, they are carried out automatically.
///
/// However, opening implicit dependencies is always done through an object of type
/// `ImplicitOpenGate`, which you pass to [`FormatDriverBuilder::open()`].  Implementing this trait
/// therefore allows you to restrict if and how images an storage objects are opened implicitly.
///
/// Anytime a storage object is opened, it is done by an [`ImplicitOpenGate::open_storage()`]
/// implementation.  Anytime a format layer is opened, it is done by an
/// [`ImplicitOpenGate::open_format()`] implementation.  Therefore, unless your implementation does
/// perform this open operation, nothing will be opened.
///
/// Do note however that whatever you open explicitly through [`FormatDriverBuilder::open()`] or
/// [`Storage::open()`] is *not* run through `ImplicitOpenGate`.
///
/// See [`PermissiveImplicitOpenGate`] and [`DenyImplicitOpenGate`].
pub trait ImplicitOpenGate<S: Storage + 'static> {
    /// Open an implicitly referenced format layer.
    ///
    /// You can e.g. check the supposed format via `F::FORMAT`, and its filename via
    /// `builder.get_image_path()`.
    ///
    /// Note that this is not invoked for images that are explicitly opened, i.e. whenever
    /// [`FormatDriverBuilder::open()`] is called by imago users.
    #[allow(async_fn_in_trait)] // No need for Send
    async fn open_format<F: FormatDriverBuilder<S>>(
        &mut self,
        builder: F,
    ) -> io::Result<FormatAccess<S>>;

    /// Open an implicitly referenced storage object.
    ///
    /// You can e.g. check the filename via `builder.get_filename()`.
    ///
    /// Note that this is not invoked for storage objects that are explicitly opened, i.e. whenever
    /// an object of type `S` is created by imago users (e.g. via [`S::open()`](Storage::open())).
    #[allow(async_fn_in_trait)] // No need for Send
    async fn open_storage(&mut self, builder: StorageOpenOptions) -> io::Result<S>;
}

/// Open all implicitly referenced images/files unrestricted, as requested.
///
/// Use with caution!  Allowing all implicit dependencies to be opened automatically without
/// restrictions is dangerous:
/// - if you plan to give access to the image to an untrusted third party (e.g. a VM guest), and
/// - unless the image comes from a fully trusted source.
///
/// This would give the untrusted third party potentially access to arbitrary storage object
/// accessible through imago.
///
/// (See also the safety section on
/// [`FormatDriverInstance::probe()`](super::drivers::FormatDriverInstance::probe()).)
#[derive(Clone, Copy, Debug, Default)]
pub struct PermissiveImplicitOpenGate();

impl<S: Storage + 'static> ImplicitOpenGate<S> for PermissiveImplicitOpenGate {
    async fn open_format<F: FormatDriverBuilder<S>>(
        &mut self,
        builder: F,
    ) -> io::Result<FormatAccess<S>> {
        // Recursion, need to box
        Ok(FormatAccess::new(
            Box::pin(builder.open(Self::default())).await?,
        ))
    }

    async fn open_storage(&mut self, builder: StorageOpenOptions) -> io::Result<S> {
        S::open(builder).await
    }
}

/// Disallow any implicitly referenced images or storage objects.
///
/// Always returns errors, ensuring nothing can be opened implicitly.  Useful when you intend to
/// explicitly override all potential implicit references, and want a safeguard that you did not
/// forget anything.
///
/// If you did forget something, the error generated by this object will most likely be propagated
/// up to the prompting [`FormatDriverBuilder::open()`] call, failing it.
#[derive(Clone, Copy, Debug, Default)]
pub struct DenyImplicitOpenGate();

impl<S: Storage + 'static> ImplicitOpenGate<S> for DenyImplicitOpenGate {
    async fn open_format<F: FormatDriverBuilder<S>>(
        &mut self,
        builder: F,
    ) -> io::Result<FormatAccess<S>> {
        let msg = if let Some(filename) = builder.get_image_path() {
            format!("Opening implicitly referenced format layer {filename:?} denied")
        } else {
            "Opening implicitly referenced format layer denied".into()
        };

        Err(io::Error::new(io::ErrorKind::PermissionDenied, msg))
    }

    async fn open_storage(&mut self, builder: StorageOpenOptions) -> io::Result<S> {
        let msg = if let Some(filename) = builder.get_filename() {
            format!("Opening implicitly referenced storage object {filename:?} denied")
        } else {
            "Opening implicitly referenced storage object denied".into()
        };

        Err(io::Error::new(io::ErrorKind::PermissionDenied, msg))
    }
}
