aboutsummaryrefslogtreecommitdiffstats
path: root/atuin-client/src/history.rs
diff options
context:
space:
mode:
authorVlad Stepanov <8uk.8ak@gmail.com>2023-06-15 14:29:40 +0400
committerGitHub <noreply@github.com>2023-06-15 10:29:40 +0000
commit4077c33adfdacaf0ed68657a1955a7b69a78d373 (patch)
tree432d5c23992388a6c1bd4b11d41785ea00d56905 /atuin-client/src/history.rs
parentAdd namespaces to kv store (#1052) (diff)
downloadatuin-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.rs150
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
}