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 {
actions: &[
Action::Setup(Setup {
command: &[
"sq", "pki", "link", "add",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--userid=Alice <alice@example.org>",
],
}),
Action::setup().command(&[
"sq", "pki", "link", "add",
"--cert=EB28F26E2739A4870ECC47726F0073F60FD0CBF0",
"--userid=Alice <alice@example.org>",
]).build(),
Action::Setup(Setup {
command: &[
"sq", "pki", "link", "add",
"--cert=511257EBBF077B7AEDAE5D093F68CB84CE537C9A",
"--userid=Bob <bob@example.org>",
],
}),
Action::setup().command(&[
"sq", "pki", "link", "add",
"--cert=511257EBBF077B7AEDAE5D093F68CB84CE537C9A",
"--userid=Bob <bob@example.org>",
]).build(),
Action::Example(Example {
comment: "Export all certificates.",
command: &[
"sq", "cert", "export", "--all",
],
}),
Action::Example(Example {
comment: "\
Export certificates with a matching User ID packet. The binding \
signatures are checked, and the User IDs are authenticated. \
Note: this check is case sensitive.",
command: &[
"sq", "cert", "export",
"--cert-userid", "Alice <alice@example.org>",
],
}),
Action::Example(Example {
comment: "\
Export certificates with a User ID containing the email address. \
The binding signatures are checked, and the User IDs are \
authenticated. Note: this check is case insensitive.",
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",
],
}),
Action::example().comment(
"Export certificates with a User ID containing the \
email address."
).command(&[
"sq", "cert", "export", "--cert-email=alice@example.org",
]).build(),
Action::example().comment(
"Export certificates that contain a User ID with *either* \
(not both!) email address."
).command(&[
"sq", "cert", "export",
"--cert-email=alice@example.org",
"--cert-email=bob@example.org",
]).build(),
Action::example().comment(
"Export all certificates."
).command(&[
"sq", "cert", "export", "--all",
]).build(),
],
};

View File

@ -14,6 +14,37 @@ pub struct Setup<'a> {
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
/// the manual pages.
pub struct Example<'a> {
@ -22,6 +53,61 @@ pub struct Example<'a> {
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.
#[allow(dead_code)]
pub enum Action<'a> {
@ -39,6 +125,16 @@ pub enum 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.
#[allow(dead_code)]
pub fn command(&self) -> Option<&'a [ &'a str ]> {