Index: Cargo.toml ================================================================== --- Cargo.toml +++ Cargo.toml @@ -1,8 +1,8 @@ [package] name = "qsu" -version = "0.0.7" +version = "0.1.0" edition = "2021" license = "0BSD" categories = [ "asynchronous" ] keywords = [ "service", "systemd", "winsvc" ] repository = "https://repos.qrnch.tech/pub/qsu" @@ -28,25 +28,26 @@ rt = [] tokio = ["rt", "tokio/macros", "tokio/rt-multi-thread", "tokio/signal"] wait-for-debugger = ["dep:dbgtools-win"] [dependencies] -async-trait = { version = "0.1.74" } -chrono = { version = "0.4.24" } -clap = { version = "4.4.11", optional = true, features = [ +apperr = { version = "0.1.0" } +async-trait = { version = "0.1.77" } +chrono = { version = "0.4.31" } +clap = { version = "4.4.14", optional = true, features = [ "derive", "env", "string", "wrap_help" ] } -env_logger = { version = "0.10.0" } -futures = { version = "0.3.29" } +env_logger = { version = "0.10.1" } +futures = { version = "0.3.30" } itertools = { version = "0.12.0", optional = true } killswitch = { version = "0.4.2" } log = { version = "0.4.20" } parking_lot = { version = "0.12.1" } rocket = { version = "0.5.0", optional = true } sidoc = { version = "0.1.0", optional = true } -tokio = { version = "1.35.0", features = ["sync"] } -time = { version = "0.3.20", features = ["macros"] } +tokio = { version = "1.35.1", features = ["sync"] } +time = { version = "0.3.31", features = ["macros"] } tracing = { version = "0.1.40" } [dependencies.tracing-subscriber] version = "0.3.18" default-features = false @@ -54,11 +55,11 @@ [target.'cfg(target_os = "linux")'.dependencies] sd-notify = { version = "0.4.1", optional = true } [target.'cfg(unix)'.dependencies] -libc = { version = "0.2.149" } +libc = { version = "0.2.152" } nix = { version = "0.27.1", features = ["pthread", "signal"] } [target.'cfg(windows)'.dependencies] dbgtools-win = { version = "0.2.1", optional = true } eventlog = { version = "0.2.2" } @@ -69,12 +70,12 @@ "Win32_Foundation", "Win32_System_Console" ] } winreg = { version = "0.52.0" } [dev-dependencies] -clap = { version = "4.4.6", features = ["derive", "env", "wrap_help"] } -tokio = { version = "1.33.0", features = ["time"] } +clap = { version = "4.4.14", features = ["derive", "env", "wrap_help"] } +tokio = { version = "1.35.1", features = ["time"] } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] Index: src/argp.rs ================================================================== --- src/argp.rs +++ src/argp.rs @@ -1,6 +1,35 @@ //! Helpers for integrating clap into an application using _qsu_. +//! +//! The purpose of the argument parser is to allow a semi-standard command line +//! interface for registering, deregistering and allowing services to run both +//! in the foreground as well as run as actual services. To accomplish this, +//! the argument parser wraps around the runtime. Specifically, this means +//! that if the argument parser is used, the application does not need to +//! launch the runtime explicitly; the argument parser will do this if the +//! appropriate arguments have been passed to it. +//! +//! Assuming a service handler has been implemented already, a simple use-case +//! for the argument parser in a server application involves: +//! 1. Implement the [`ArgsProc`] trait on an application object, and implement +//! the [`ArgsProc::build_apprt()`] method. This method should return a +//! [`SrvAppRt`] object, containing the service handler. +//! 2. Create an instance of the [`ArgParser`], passing in the service name and +//! a reference to an [`ArgsProc`] object. +//! 3. Call [`ArgParser::proc()`] to process the command line arguments. +//! +//! The argument parser will determine whether the caller requested to +//! register/deregister the service, run the server application in foreground +//! mode or as a system service. +//! +//! # Customization +//! While the argument parser in _qsu_ is rather opinionated, it does allow +//! for _some_ customization. The names of the subcommands to (de)register or +//! run the server application as a service can be modified by the +//! application. It is possible to register custom subcommands, and the +//! command line parser specification can be modified by the [`ArgsProc`] +//! callbacks. use clap::{builder::Str, Arg, ArgAction, ArgMatches, Args, Command}; use crate::{ err::{AppErr, Error}, @@ -194,10 +223,12 @@ Self { svcname } } } +/// Used to differentiate between running without a subcommand, or the +/// install/uninstall or run service subcommands. pub enum Cmd { Root, Inst, Rm, Run Index: src/err.rs ================================================================== --- src/err.rs +++ src/err.rs @@ -1,6 +1,8 @@ -use std::{any::Any, fmt, io}; +use std::{fmt, io}; + +pub use apperr::AppErr; #[derive(Debug)] pub enum ArgsError { #[cfg(feature = "clap")] Clap(clap::Error), @@ -235,89 +237,13 @@ fn from(err: windows_service::Error) -> Self { Error::SubSystem(err.to_string()) } } - -/// An error type used to store an application-specific error. -/// -/// Application call-backs return this type for the `Err()` case in order to -/// allow the errors to be passed back to the application call that started the -/// service runtime. -#[repr(transparent)] -#[derive(Debug)] -pub struct AppErr(Box); - -impl AppErr { - pub fn new(e: E) -> Self - where - E: Send + 'static - { - Self(Box::new(e)) - } - - /// Inspect error type wrapped by the `AppErr`. - pub fn is(&self) -> bool - where - T: Any - { - self.0.is::() - } - - /// Attempt to unpack and cast the inner error type. - /// - /// If it can't be downcast to `E`, `AppErr` will be returned in the `Err()` - /// case. - /// - /// ``` - /// use qsu::AppErr; - /// - /// enum MyErr { - /// Something(String) - /// } - /// let apperr = AppErr::new(MyErr::Something("hello".into())); - /// - /// let Ok(e) = apperr.try_into_inner::() else { - /// panic!("Unexpectedly not MyErr"); - /// }; - /// ``` - pub fn try_into_inner(self) -> Result { - match self.0.downcast::() { - Ok(e) => Ok(*e), - Err(e) => Err(AppErr(e)) - } - } - - /// Unwrap application-specific error from an [`Error`](crate::err::Error). - /// - /// ``` - /// use qsu::AppErr; - /// - /// enum MyErr { - /// Something(String) - /// } - /// let apperr = AppErr::new(MyErr::Something("hello".into())); - /// - /// let MyErr::Something(e) = apperr.unwrap_inner::() else { - /// panic!("Unexpectedly not MyErr::Something"); - /// }; - /// assert_eq!(e, "hello"); - /// ``` - /// - /// # Panic - /// Panics if the inner type is not castable to `E`. - pub fn unwrap_inner(self) -> E { - let Ok(e) = self.0.downcast::() else { - panic!("Unable to downcast to error type E"); - }; - *e - } -} - impl From for Error { /// Wrap an [`AppErr`] in an [`Error`]. fn from(err: AppErr) -> Self { Error::App(err) } } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : Index: src/lib.rs ================================================================== --- src/lib.rs +++ src/lib.rs @@ -1,8 +1,8 @@ //! _qsu_ is a set of tools for integrating a server application against a //! service subsystem (such as -//! [Windows Services](https://learn.microsoft.com/en-us/windows/win32/services/services) [systemd](https://systemd.io/), or +//! [Windows Services](https://learn.microsoft.com/en-us/windows/win32/services/services), [systemd](https://systemd.io/), or //! [launchd](https://www.launchd.info/)). //! //! It offers a thin runtime wrapper layer with the purpose of abstracting away //! differences between service subsystems (and also provides the same //! interface when running the server application as a foreground process). Index: www/changelog.md ================================================================== --- www/changelog.md +++ www/changelog.md @@ -6,10 +6,19 @@ ### Changed ### Removed +--- + +## [0.1.0] - 2024-01-10 + +### Changed + +- Use apperr crate for `AppErr` instead of using a custom in-tree + implementation. + --- ## [0.0.7] - 2023-12-09 ### Changed