diff options
| author | Vlad Stepanov <8uk.8ak@gmail.com> | 2023-06-15 14:29:40 +0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-15 10:29:40 +0000 |
| commit | 4077c33adfdacaf0ed68657a1955a7b69a78d373 (patch) | |
| tree | 432d5c23992388a6c1bd4b11d41785ea00d56905 /atuin-client/src/history.rs | |
| parent | Add namespaces to kv store (#1052) (diff) | |
| download | atuin-4077c33adfdacaf0ed68657a1955a7b69a78d373.zip | |
Builder interface for History objects (#933)
* [feature] store env variables in History records
WIP: remove `HistoryWithoutDelete`, add some docstrings, tests
* Create History objects through builders.
Assure in compile-time that all required fields
are set for the given construction scenario
* (from #882) split Cmd::run into subfns
* Update `History` doc
* remove rmp-serde from history
* update warning
---------
Co-authored-by: Conrad Ludgate <conrad.ludgate@truelayer.com>
Diffstat (limited to 'atuin-client/src/history.rs')
| -rw-r--r-- | atuin-client/src/history.rs | 150 |
1 files changed, 131 insertions, 19 deletions
diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs index 05bfbf7f..441960c8 100644 --- a/atuin-client/src/history.rs +++ b/atuin-client/src/history.rs @@ -1,42 +1,51 @@ use std::env; use chrono::Utc; -use serde::{Deserialize, Serialize}; use atuin_common::utils::uuid_v7; -// Any new fields MUST be Optional<>! -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, sqlx::FromRow)] +mod builder; + +/// Client-side history entry. +/// +/// Client stores data unencrypted, and only encrypts it before sending to the server. +/// +/// To create a new history entry, use one of the builders: +/// - [`History::import()`] to import an entry from the shell history file +/// - [`History::capture()`] to capture an entry via hook +/// - [`History::from_db()`] to create an instance from the database entry +// +// ## Implementation Notes +// +// New fields must should be added to `encryption::{encode, decode}` in a backwards +// compatible way. (eg sensible defaults and updating the nfields parameter) +#[derive(Debug, Clone, PartialEq, Eq, sqlx::FromRow)] pub struct History { + /// A client-generated ID, used to identify the entry when syncing. + /// + /// Stored as `client_id` in the database. pub id: String, + /// When the command was run. pub timestamp: chrono::DateTime<Utc>, + /// How long the command took to run. pub duration: i64, + /// The exit code of the command. pub exit: i64, + /// The command that was run. pub command: String, + /// The current working directory when the command was run. pub cwd: String, + /// The session ID, associated with a terminal session. pub session: String, + /// The hostname of the machine the command was run on. pub hostname: String, + /// Timestamp, which is set when the entry is deleted, allowing a soft delete. pub deleted_at: Option<chrono::DateTime<Utc>>, } -// Forgive me, for I have sinned -// I need to replace rmp with something that is more backwards-compatible. -// Protobuf, or maybe just json -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, sqlx::FromRow)] -pub struct HistoryWithoutDelete { - pub id: String, - pub timestamp: chrono::DateTime<Utc>, - pub duration: i64, - pub exit: i64, - pub command: String, - pub cwd: String, - pub session: String, - pub hostname: String, -} - impl History { #[allow(clippy::too_many_arguments)] - pub fn new( + fn new( timestamp: chrono::DateTime<Utc>, command: String, cwd: String, @@ -70,6 +79,109 @@ impl History { } } + /// Builder for a history entry that is imported from shell history. + /// + /// The only two required fields are `timestamp` and `command`. + /// + /// ## Examples + /// ``` + /// use atuin_client::history::History; + /// + /// let history: History = History::import() + /// .timestamp(chrono::Utc::now()) + /// .command("ls -la") + /// .build() + /// .into(); + /// ``` + /// + /// If shell history contains more information, it can be added to the builder: + /// ``` + /// use atuin_client::history::History; + /// + /// let history: History = History::import() + /// .timestamp(chrono::Utc::now()) + /// .command("ls -la") + /// .cwd("/home/user") + /// .exit(0) + /// .duration(100) + /// .build() + /// .into(); + /// ``` + /// + /// Unknown command or command without timestamp cannot be imported, which + /// is forced at compile time: + /// + /// ```compile_fail + /// use atuin_client::history::History; + /// + /// // this will not compile because timestamp is missing + /// let history: History = History::import() + /// .command("ls -la") + /// .build() + /// .into(); + /// ``` + pub fn import() -> builder::HistoryImportedBuilder { + builder::HistoryImported::builder() + } + + /// Builder for a history entry that is captured via hook. + /// + /// This builder is used only at the `start` step of the hook, + /// so it doesn't have any fields which are known only after + /// the command is finished, such as `exit` or `duration`. + /// + /// ## Examples + /// ```rust + /// use atuin_client::history::History; + /// + /// let history: History = History::capture() + /// .timestamp(chrono::Utc::now()) + /// .command("ls -la") + /// .cwd("/home/user") + /// .build() + /// .into(); + /// ``` + /// + /// Command without any required info cannot be captured, which is forced at compile time: + /// + /// ```compile_fail + /// use atuin_client::history::History; + /// + /// // this will not compile because `cwd` is missing + /// let history: History = History::capture() + /// .timestamp(chrono::Utc::now()) + /// .command("ls -la") + /// .build() + /// .into(); + /// ``` + pub fn capture() -> builder::HistoryCapturedBuilder { + builder::HistoryCaptured::builder() + } + + /// Builder for a history entry that is imported from the database. + /// + /// All fields are required, as they are all present in the database. + /// + /// ```compile_fail + /// use atuin_client::history::History; + /// + /// // this will not compile because `id` field is missing + /// let history: History = History::from_db() + /// .timestamp(chrono::Utc::now()) + /// .command("ls -la".to_string()) + /// .cwd("/home/user".to_string()) + /// .exit(0) + /// .duration(100) + /// .session("somesession".to_string()) + /// .hostname("localhost".to_string()) + /// .deleted_at(None) + /// .build() + /// .into(); + /// ``` + pub fn from_db() -> builder::HistoryFromDbBuilder { + builder::HistoryFromDb::builder() + } + pub fn success(&self) -> bool { self.exit == 0 || self.duration == -1 } |
