Add a builder-style interface to the example framework.

- Also, port the examples for `sq cert export` over, and thin them
    out a little (see #451).
This commit is contained in:
Justus Winter 2024-11-20 16:42:43 +01:00
parent 797ab7a003
commit 356781e535
No known key found for this signature in database
GPG Key ID: 686F55B4AB2B3386
2 changed files with 127 additions and 57 deletions

View File

@ -9,65 +9,39 @@ use crate::cli::examples::*;
const EXAMPLES: Actions = Actions { const EXAMPLES: Actions = Actions {
actions: &[ actions: &[
Action::Setup(Setup { Action::setup().command(&[
command: &[ "sq", "pki", "link", "add",
"sq", "pki", "link", "add", "--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0", "--userid=Alice <alice@example.org>",
"--userid=Alice <alice@example.org>", ]).build(),
],
}),
Action::Setup(Setup { Action::setup().command(&[
command: &[ "sq", "pki", "link", "add",
"sq", "pki", "link", "add", "--cert=511257EBBF077B7AEDAE5D093F68CB84CE537C9A",
"--cert=511257EBBF077B7AEDAE5D093F68CB84CE537C9A", "--userid=Bob <bob@example.org>",
"--userid=Bob <bob@example.org>", ]).build(),
],
}),
Action::Example(Example { Action::example().comment(
comment: "Export all certificates.", "Export certificates with a User ID containing the \
command: &[ email address."
"sq", "cert", "export", "--all", ).command(&[
], "sq", "cert", "export", "--cert-email=alice@example.org",
}), ]).build(),
Action::Example(Example {
comment: "\ Action::example().comment(
Export certificates with a matching User ID packet. The binding \ "Export certificates that contain a User ID with *either* \
signatures are checked, and the User IDs are authenticated. \ (not both!) email address."
Note: this check is case sensitive.", ).command(&[
command: &[ "sq", "cert", "export",
"sq", "cert", "export", "--cert-email=alice@example.org",
"--cert-userid", "Alice <alice@example.org>", "--cert-email=bob@example.org",
], ]).build(),
}),
Action::Example(Example { Action::example().comment(
comment: "\ "Export all certificates."
Export certificates with a User ID containing the email address. \ ).command(&[
The binding signatures are checked, and the User IDs are \ "sq", "cert", "export", "--all",
authenticated. Note: this check is case insensitive.", ]).build(),
command: &[
"sq", "cert", "export", "--cert-email", "alice@example.org",
],
}),
Action::Example(Example {
comment: "\
Export certificates where a certificate's primary key or a subkey \
has the specified Key ID.",
command: &[
"sq", "cert", "export", "--cert", "6F0073F60FD0CBF0",
],
}),
Action::Example(Example {
comment: "\
Export certificates that contain a User ID with *either* (not both!) \
email address. Note: this check is case insensitive.",
command: &[
"sq", "cert", "export",
"--cert-email", "alice@example.org",
"--cert-email", "bob@example.org",
],
}),
], ],
}; };

View File

@ -14,6 +14,37 @@ pub struct Setup<'a> {
pub command: &'a [ &'a str ], pub command: &'a [ &'a str ],
} }
/// Builds up setup actions in an extensible way.
pub struct SetupBuilder<'a> {
setup: Setup<'a>,
}
impl<'a> SetupBuilder<'a> {
/// Returns a new setup builder.
const fn new() -> Self {
SetupBuilder {
setup: Setup {
command: &[],
}
}
}
/// Provides the command as slice.
///
/// It'd be nice to provide a per-argument interface, but that
/// requires some ingenuity for it to stay const.
pub const fn command(mut self, command: &'a [&'a str]) -> Self {
self.setup.command = command;
self
}
/// Finishes building the setup action.
pub const fn build(self) -> Action<'a> {
assert!(! self.setup.command.is_empty());
Action::Setup(self.setup)
}
}
/// A command that is executed by the integration test, and shown in /// A command that is executed by the integration test, and shown in
/// the manual pages. /// the manual pages.
pub struct Example<'a> { pub struct Example<'a> {
@ -22,6 +53,61 @@ pub struct Example<'a> {
pub command: &'a [ &'a str ], pub command: &'a [ &'a str ],
} }
/// Builds up example actions in an extensible way.
pub struct ExampleBuilder<'a> {
example: Example<'a>,
}
impl<'a> ExampleBuilder<'a> {
/// Returns a new example builder.
const fn new() -> Self {
ExampleBuilder {
example: Example {
comment: "",
command: &[],
}
}
}
/// Provides the comment.
///
/// It'd be nice to provide a per-argument interface, but that
/// requires some ingenuity for it to stay const.
pub const fn comment(mut self, comment: &'a str) -> Self {
self.example.comment = comment;
self
}
/// Provides the command as slice.
///
/// It'd be nice to provide a per-argument interface, but that
/// requires some ingenuity for it to stay const.
pub const fn command(mut self, command: &'a [&'a str]) -> Self {
self.example.command = command;
self
}
/// Finishes building the example action.
///
/// The example will be executed by the test.
pub const fn build(self) -> Action<'a> {
assert!(! self.example.comment.is_empty());
assert!(! self.example.command.is_empty());
Action::Example(self.example)
}
/// Finishes building the example action, marking it for syntax
/// checking only.
///
/// The example will not be executed by the test, but the syntax
/// will be checked using our command line parser.
pub const fn syntax_check(self) -> Action<'a> {
assert!(! self.example.comment.is_empty());
assert!(! self.example.command.is_empty());
Action::SyntaxCheck(self.example)
}
}
/// An action to execute. /// An action to execute.
#[allow(dead_code)] #[allow(dead_code)]
pub enum Action<'a> { pub enum Action<'a> {
@ -39,6 +125,16 @@ pub enum Action<'a> {
} }
impl<'a> Action<'a> { impl<'a> Action<'a> {
/// Creates a setup action.
pub const fn setup() -> SetupBuilder<'a> {
SetupBuilder::new()
}
/// Creates an example action.
pub const fn example() -> ExampleBuilder<'a> {
ExampleBuilder::new()
}
/// Return the action's command, if any. /// Return the action's command, if any.
#[allow(dead_code)] #[allow(dead_code)]
pub fn command(&self) -> Option<&'a [ &'a str ]> { pub fn command(&self) -> Option<&'a [ &'a str ]> {