use crate::{
    config,
    config::tree::{Gitoxide, Key, Section, keys},
};

impl Gitoxide {
    /// The `gitoxide.allow` section.
    pub const ALLOW: Allow = Allow;
    /// The `gitoxide.author` section.
    pub const AUTHOR: Author = Author;
    /// The `gitoxide.core` section.
    pub const CORE: Core = Core;
    /// The `gitoxide.commit` section.
    pub const COMMIT: Commit = Commit;
    /// The `gitoxide.committer` section.
    pub const COMMITTER: Committer = Committer;
    /// The `gitoxide.credentials` section.
    pub const CREDENTIALS: Credentials = Credentials;
    /// The `gitoxide.http` section.
    pub const HTTP: Http = Http;
    /// The `gitoxide.https` section.
    pub const HTTPS: Https = Https;
    /// The `gitoxide.objects` section.
    pub const OBJECTS: Objects = Objects;
    /// The `gitoxide.ssh` section.
    pub const SSH: Ssh = Ssh;
    /// The `gitoxide.user` section.
    pub const USER: User = User;
    /// The `gitoxide.pathspec` section.
    pub const PATHSPEC: Pathspec = Pathspec;

    /// The `gitoxide.userAgent` Key.
    pub const USER_AGENT: keys::Any = keys::Any::new("userAgent", &config::Tree::GITOXIDE).with_note(
        "The user agent presented on the git protocol layer, serving as fallback for when no `http.userAgent` is set",
    );

    /// The `gitoxide.tracePacket` Key.
    pub const TRACE_PACKET: keys::Boolean = keys::Boolean::new_boolean("tracePacket", &config::Tree::GITOXIDE)
        .with_environment_override("GIT_TRACE_PACKET");
    /// The `gitoxide.parsePrecious` Key.
    pub const PARSE_PRECIOUS: keys::Boolean = keys::Boolean::new_boolean("parsePrecious", &config::Tree::GITOXIDE)
        .with_environment_override("GIX_PARSE_PRECIOUS");
}

impl Section for Gitoxide {
    fn name(&self) -> &str {
        "gitoxide"
    }

    fn keys(&self) -> &[&dyn Key] {
        &[&Self::USER_AGENT, &Self::TRACE_PACKET, &Self::PARSE_PRECIOUS]
    }

    fn sub_sections(&self) -> &[&dyn Section] {
        &[
            &Self::ALLOW,
            &Self::AUTHOR,
            &Self::CORE,
            &Self::COMMIT,
            &Self::COMMITTER,
            &Self::CREDENTIALS,
            &Self::HTTP,
            &Self::HTTPS,
            &Self::OBJECTS,
            &Self::SSH,
            &Self::USER,
            &Self::PATHSPEC,
        ]
    }
}

mod subsections {
    use crate::{
        bstr::ByteSlice,
        config::{
            Tree,
            tree::{Gitoxide, Key, Section, http, keys},
        },
    };

    /// The `Core` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Core;

    /// The `gitoxide.core.refsNamespace` key.
    pub type RefsNamespace = keys::Any<super::validate::RefsNamespace>;
    /// A path to an alternate index file.
    pub type IndexFile = keys::Any<super::validate::NonEmptyPath>;

    impl RefsNamespace {
        /// Derive the negotiation algorithm identified by `name`, case-sensitively.
        pub fn try_into_refs_namespace(
            &'static self,
            name: impl gix_utils::AsBStr,
        ) -> Result<gix_ref::Namespace, crate::config::refs_namespace::Error> {
            let name = name.as_bstr();
            gix_ref::namespace::expand(name.as_bstr())
                .map_err(|err| crate::config::key::Error::from_value(self, name.into()).with_source(err))
        }
    }

    impl Core {
        /// The `gitoxide.core.defaultPackCacheMemoryLimit` key.
        pub const DEFAULT_PACK_CACHE_MEMORY_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("defaultPackCacheMemoryLimit", &Gitoxide::CORE).with_note(
                "If unset, we default to 96MB memory cap for the default 64 slot LRU cache for object deltas.",
            );
        /// The `gitoxide.core.useNsec` key.
        pub const USE_NSEC: keys::Boolean = keys::Boolean::new_boolean("useNsec", &Gitoxide::CORE)
            .with_note("A runtime version of the USE_NSEC build flag.");

        /// The `gitoxide.core.useStdev` key.
        pub const USE_STDEV: keys::Boolean = keys::Boolean::new_boolean("useStdev", &Gitoxide::CORE)
            .with_note("A runtime version of the USE_STDEV build flag.");

        /// The `gitoxide.core.protectWindows` key.
        pub const PROTECT_WINDOWS: keys::Boolean = keys::Boolean::new_boolean("protectWindows", &Gitoxide::CORE)
            .with_note("enable protections that are enabled by default on Windows");

        /// The `gitoxide.core.shallowFile` key.
        pub const SHALLOW_FILE: keys::Path = keys::Path::new_path("shallowFile", &Gitoxide::CORE)
            .with_default(b"shallow")
            .with_environment_override("GIT_SHALLOW_FILE")
            .with_deviation(
                "relative file paths will always be made relative to the git-common-dir, whereas `git` keeps them as is.",
            );

        /// The `gitoxide.core.indexFile` key, which selects an alternate index file.
        ///
        /// `GIT_INDEX_FILE` is mapped to this key when environment overrides are enabled and takes precedence over
        /// configured values. An empty value is invalid.
        pub const INDEX_FILE: IndexFile =
            keys::Any::new_with_validate("indexFile", &Gitoxide::CORE, super::validate::NonEmptyPath)
            .with_environment_override("GIT_INDEX_FILE")
            .with_deviation(
                "relative file paths are resolved against the current working directory captured when the repository was opened, whereas Git retains them and thus resolves them against the process's current directory on each use.",
            );

        /// The `gitoxide.core.filterProcessDelay` key (default `true`).
        ///
        /// It controls whether or not long running filter driver processes can use the 'delay' capability.
        pub const FILTER_PROCESS_DELAY: keys::Boolean =
            keys::Boolean::new_boolean("filterProcessDelay", &Gitoxide::CORE);

        /// The `gitoxide.core.externalCommandStderr` key (default `true`).
        ///
        /// If `true`, the default, `stderr` of worktree filter programs, or any other git-context bearing command
        /// invoked will be inherited.
        /// If `false`, it will be suppressed completely.
        pub const EXTERNAL_COMMAND_STDERR: keys::Boolean =
            keys::Boolean::new_boolean("externalCommandStderr", &Gitoxide::CORE)
                .with_environment_override("GIX_EXTERNAL_COMMAND_STDERR");

        /// The `gitoxide.core.refsNamespace` key.
        pub const REFS_NAMESPACE: RefsNamespace =
            keys::Any::new_with_validate("refsNamespace", &Gitoxide::CORE, super::validate::RefsNamespace)
                .with_environment_override("GIT_NAMESPACE");
    }

    impl Section for Core {
        fn name(&self) -> &str {
            "core"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::DEFAULT_PACK_CACHE_MEMORY_LIMIT,
                &Self::USE_NSEC,
                &Self::USE_STDEV,
                &Self::SHALLOW_FILE,
                &Self::INDEX_FILE,
                &Self::PROTECT_WINDOWS,
                &Self::FILTER_PROCESS_DELAY,
                &Self::EXTERNAL_COMMAND_STDERR,
                &Self::REFS_NAMESPACE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `Http` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Http;

    impl Http {
        /// The `gitoxide.http.proxy` key.
        pub const PROXY: keys::String =
            keys::String::new_string("proxy", &Gitoxide::HTTP).with_environment_override("http_proxy");
        /// The `gitoxide.http.allProxy` key.
        pub const ALL_PROXY: keys::String = keys::String::new_string("allProxy", &Gitoxide::HTTP)
            .with_environment_override("all_proxy")
            .with_note("fallback environment is `ALL_PROXY`");
        /// The `gitoxide.http.verbose` key.
        ///
        /// If set, curl will be configured to log verbosely.
        pub const VERBOSE: keys::Boolean = keys::Boolean::new_boolean("verbose", &Gitoxide::HTTP)
            .with_environment_override("GIT_CURL_VERBOSE")
            .with_deviation("we parse it as boolean for convenience (infallible) but git only checks the presence");
        /// The `gitoxide.http.noProxy` key.
        pub const NO_PROXY: keys::String = keys::String::new_string("noProxy", &Gitoxide::HTTP)
            .with_environment_override("no_proxy")
            .with_note("fallback environment is `NO_PROXY`");
        /// The `gitoxide.http.connectTimeout` key.
        pub const CONNECT_TIMEOUT: keys::DurationInMilliseconds =
            keys::DurationInMilliseconds::new_duration("connectTimeout", &Gitoxide::HTTP).with_note(
                "entirely new, and in milliseconds, to describe how long to wait until a connection attempt is aborted",
            );
        /// The `gitoxide.http.sslVersionMin` key.
        pub const SSL_VERSION_MIN: http::SslVersion =
            http::SslVersion::new_ssl_version("sslVersionMin", &Gitoxide::HTTP).with_note(
                "entirely new to set the lower bound for the allowed ssl version range. Overwrites the min bound of `http.sslVersion` if set. Min and Max must be set to become effective.",
            );
        /// The `gitoxide.http.sslVersionMax` key.
        pub const SSL_VERSION_MAX: http::SslVersion =
            http::SslVersion::new_ssl_version("sslVersionMax", &Gitoxide::HTTP).with_note(
                "entirely new to set the upper bound for the allowed ssl version range. Overwrites the max bound of `http.sslVersion` if set. Min and Max must be set to become effective.",
            );
        /// The `gitoxide.http.sslNoVerify` key.
        ///
        /// If set, disable SSL verification. Using this is discouraged as it can lead to
        /// various security risks. An example where this may be needed is when an internal
        /// git server uses a self-signed certificate and the user accepts the associated security risks.
        pub const SSL_NO_VERIFY: keys::Boolean = keys::Boolean::new_boolean("sslNoVerify", &Gitoxide::HTTP)
            .with_environment_override("GIT_SSL_NO_VERIFY")
            .with_note("used to disable SSL verification. When this is enabled it takes priority over http.sslVerify");
        /// The `gitoxide.http.proxyAuthMethod` key.
        pub const PROXY_AUTH_METHOD: http::ProxyAuthMethod =
            http::ProxyAuthMethod::new_proxy_auth_method("proxyAuthMethod", &Gitoxide::HTTP)
                .with_environment_override("GIT_HTTP_PROXY_AUTHMETHOD");
    }

    impl Section for Http {
        fn name(&self) -> &str {
            "http"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::PROXY,
                &Self::ALL_PROXY,
                &Self::VERBOSE,
                &Self::NO_PROXY,
                &Self::CONNECT_TIMEOUT,
                &Self::SSL_VERSION_MIN,
                &Self::SSL_VERSION_MAX,
                &Self::SSL_NO_VERIFY,
                &Self::PROXY_AUTH_METHOD,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `Https` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Https;

    impl Https {
        /// The `gitoxide.https.proxy` key.
        pub const PROXY: keys::String = keys::String::new_string("proxy", &Gitoxide::HTTPS)
            .with_environment_override("HTTPS_PROXY")
            .with_note("fallback environment variable is `https_proxy`");
    }

    impl Section for Https {
        fn name(&self) -> &str {
            "https"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::PROXY]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `allow` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Allow;

    /// The `gitoxide.allow.protocolFromUser` key.
    pub type ProtocolFromUser = keys::Boolean;

    impl Allow {
        /// The `gitoxide.allow.protocol` key, mapped from `GIT_ALLOW_PROTOCOL`.
        pub const PROTOCOL: keys::Any =
            keys::Any::new("protocol", &Gitoxide::ALLOW).with_environment_override("GIT_ALLOW_PROTOCOL");
        /// The `gitoxide.allow.protocolFromUser` key.
        pub const PROTOCOL_FROM_USER: ProtocolFromUser =
            ProtocolFromUser::new_boolean("protocolFromUser", &Gitoxide::ALLOW)
                .with_environment_override("GIT_PROTOCOL_FROM_USER");
    }

    impl Section for Allow {
        fn name(&self) -> &str {
            "allow"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::PROTOCOL, &Self::PROTOCOL_FROM_USER]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `author` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Author;

    impl Author {
        /// The `gitoxide.author.nameFallback` key.
        pub const NAME_FALLBACK: keys::Any =
            keys::Any::new("nameFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_NAME");
        /// The `gitoxide.author.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::AUTHOR).with_environment_override("GIT_AUTHOR_EMAIL");
    }

    impl Section for Author {
        fn name(&self) -> &str {
            "author"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `user` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct User;

    impl User {
        /// The `gitoxide.user.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::USER).with_environment_override("EMAIL");
    }

    impl Section for User {
        fn name(&self) -> &str {
            "user"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `ssh` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Ssh;

    impl Ssh {
        /// The `gitoxide.ssh.commandWithoutShellFallback` key.
        pub const COMMAND_WITHOUT_SHELL_FALLBACK: keys::Executable =
            keys::Executable::new_executable("commandWithoutShellFallback", &Gitoxide::SSH)
                .with_environment_override("GIT_SSH")
                .with_note("is always executed without shell and treated as fallback");
    }

    impl Section for Ssh {
        fn name(&self) -> &str {
            "ssh"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::COMMAND_WITHOUT_SHELL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `pathspec` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Pathspec;

    impl Pathspec {
        /// The `gitoxide.pathspec.glob` key.
        pub const GLOB: keys::Boolean = keys::Boolean::new_boolean("glob", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_GLOB_PATHSPECS")
            .with_note("pathspec wildcards don't match the slash character, then needing '**' to get past them");
        /// The `gitoxide.pathspec.noglob` key.
        pub const NOGLOB: keys::Boolean = keys::Boolean::new_boolean("noglob", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_NOGLOB_PATHSPECS")
            .with_note("Enable literal matching for glob patterns, effectively disabling globbing");
        /// The `gitoxide.pathspec.literal` key.
        pub const LITERAL: keys::Boolean = keys::Boolean::new_boolean("literal", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_LITERAL_PATHSPECS")
            .with_note("Make the entire spec used verbatim, the only way to get ':()name' verbatim for instance");
        /// The `gitoxide.pathspec.icase` key.
        pub const ICASE: keys::Boolean = keys::Boolean::new_boolean("icase", &Gitoxide::PATHSPEC)
            .with_environment_override("GIT_ICASE_PATHSPECS")
            .with_note("Compare string in a case-insensitive manner");
        /// The `gitoxide.pathspec.inheritIgnoreCase` key, defaulting to `true` if unspecified.
        /// If set, pathspecs will automatically be match case-insensitively if the underlying filesystem is configured that way.
        pub const INHERIT_IGNORE_CASE: keys::Boolean =
            keys::Boolean::new_boolean("inheritIgnoreCase", &Gitoxide::PATHSPEC)
                .with_note("Inherit `core.ignoreCase` for defaults in pathspecs");
        /// The default value for `gitoxide.pathspec.inheritIgnoreCase`.
        pub const INHERIT_IGNORE_CASE_DEFAULT: bool = true;
    }

    impl Section for Pathspec {
        fn name(&self) -> &str {
            "pathspec"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::GLOB,
                &Self::NOGLOB,
                &Self::LITERAL,
                &Self::ICASE,
                &Self::INHERIT_IGNORE_CASE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `objects` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Objects;

    impl Objects {
        /// The `gitoxide.objects.cacheLimit` key.
        pub const CACHE_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("cacheLimit", &Gitoxide::OBJECTS)
                .with_note("If unset or 0, there is no object cache")
                .with_environment_override("GIX_OBJECT_CACHE_MEMORY");
        /// The `gitoxide.objects.allocLimit` key.
        ///
        /// Implemented for:
        /// - packed object decoding in `gix-pack::data::File`
        /// - delta-tree packed object traversal in `gix-pack::index`
        /// - multi-pack-index name decoding in `gix-pack::multi_index::File`
        /// - direct packed-object inflation in `gix-odb`
        pub const ALLOC_LIMIT: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("allocLimit", &Gitoxide::OBJECTS)
                .with_environment_override("GIT_ALLOC_LIMIT")
                .with_note("The maximum size of a single allocation caused by user-controlled on-disk object data. In fact, it's used for all untrusted data that causes allocations.");
        /// The default `allocLimitIfReducedTrust` in bytes applied when a repository is opened with reduced trust
        /// and no explicit [`ALLOC_LIMIT`][Self::ALLOC_LIMIT] exists.
        pub const ALLOC_LIMIT_IF_REDUCED_TRUST_DEFAULT: usize = 16 * 1024 * 1024;
        /// The `gitoxide.objects.allocLimitIfReducedTrust` key.
        ///
        /// If set, repositories opened with reduced trust receive [`ALLOC_LIMIT`][Self::ALLOC_LIMIT] set to this value
        /// when no explicit allocation limit exists. A value of `0` disables this fallback.
        pub const ALLOC_LIMIT_IF_REDUCED_TRUST: keys::UnsignedInteger =
            keys::UnsignedInteger::new_unsigned_integer("allocLimitIfReducedTrust", &Gitoxide::OBJECTS).with_note(
                "The default allocation limit used for reduced-trust repositories when no explicit allocLimit is configured; set to 0 to disable it",
            );
        /// The `gitoxide.objects.noReplace` key.
        pub const NO_REPLACE: keys::Boolean = keys::Boolean::new_boolean("noReplace", &Gitoxide::OBJECTS);
        /// The `gitoxide.objects.replaceRefBase` key.
        pub const REPLACE_REF_BASE: keys::Any = keys::Any::new("replaceRefBase", &Gitoxide::OBJECTS)
            .with_default(b"refs/replace/")
            .with_environment_override("GIT_REPLACE_REF_BASE");
    }

    impl Section for Objects {
        fn name(&self) -> &str {
            "objects"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[
                &Self::CACHE_LIMIT,
                &Self::ALLOC_LIMIT,
                &Self::ALLOC_LIMIT_IF_REDUCED_TRUST,
                &Self::REPLACE_REF_BASE,
            ]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `committer` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Committer;

    impl Committer {
        /// The `gitoxide.committer.nameFallback` key.
        pub const NAME_FALLBACK: keys::Any =
            keys::Any::new("nameFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_NAME");
        /// The `gitoxide.committer.emailFallback` key.
        pub const EMAIL_FALLBACK: keys::Any =
            keys::Any::new("emailFallback", &Gitoxide::COMMITTER).with_environment_override("GIT_COMMITTER_EMAIL");
    }

    impl Section for Committer {
        fn name(&self) -> &str {
            "committer"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::NAME_FALLBACK, &Self::EMAIL_FALLBACK]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `credentials` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Credentials;
    impl Credentials {
        /// The `gitoxide.credentials.terminalPrompt` key.
        pub const TERMINAL_PROMPT: keys::Boolean = keys::Boolean::new_boolean("terminalPrompt", &Gitoxide::CREDENTIALS)
            .with_note("This is a custom addition to provide an alternative to the respective environment variable.")
            .with_environment_override("GIT_TERMINAL_PROMPT");

        /// The `gitoxide.credentials.helperStderr` key to control what happens with the credential helpers `stderr`.
        ///
        /// If `true`, the default, `stderr` of credential helper programs will be inherited, just like with `git`.
        /// If `false`, will be suppressed completely.
        pub const HELPER_STDERR: keys::Boolean = keys::Boolean::new_boolean("helperStderr", &Gitoxide::CREDENTIALS)
            .with_environment_override("GIX_CREDENTIALS_HELPER_STDERR");
    }

    impl Section for Credentials {
        fn name(&self) -> &str {
            "credentials"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[&Self::TERMINAL_PROMPT, &Self::HELPER_STDERR]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }

    /// The `commit` sub-section.
    #[derive(Copy, Clone, Default)]
    pub struct Commit;

    impl Commit {
        /// The `gitoxide.commit.authorDate` key.
        pub const AUTHOR_DATE: keys::Any =
            keys::Any::new("authorDate", &Gitoxide::COMMIT).with_environment_override("GIT_AUTHOR_DATE");
        /// The `gitoxide.commit.committerDate` key.
        pub const COMMITTER_DATE: keys::Any =
            keys::Any::new("committerDate", &Gitoxide::COMMIT).with_environment_override("GIT_COMMITTER_DATE");
    }

    impl Section for Commit {
        fn name(&self) -> &str {
            "commit"
        }

        fn keys(&self) -> &[&dyn Key] {
            &[]
        }

        fn parent(&self) -> Option<&dyn Section> {
            Some(&Tree::GITOXIDE)
        }
    }
}
pub use subsections::{Allow, Author, Commit, Committer, Core, Credentials, Http, Https, Objects, Pathspec, Ssh, User};

pub mod validate {
    use gix_error::ValidationError;
    use std::error::Error;

    use crate::{bstr::BStr, config::tree::keys::Validate};

    #[derive(Clone, Copy)]
    pub struct RefsNamespace;
    impl Validate for RefsNamespace {
        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
            super::Core::REFS_NAMESPACE.try_into_refs_namespace(value)?;
            Ok(())
        }
    }

    #[derive(Clone, Copy)]
    pub struct NonEmptyPath;
    impl Validate for NonEmptyPath {
        fn validate(&self, value: &BStr) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
            if value.is_empty() {
                return Err(ValidationError::new("index file path must not be empty").into());
            }
            Ok(())
        }
    }
}
