-
Notifications
You must be signed in to change notification settings - Fork 1
Add Core Workspace Storage Service base #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| [workspace] | ||
| resolver = "2" | ||
|
|
||
| members = [ | ||
| members = ["apps/frivault-cli", | ||
| "crates/frilvault-core" | ||
| ] | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [package] | ||
| name = "frivault-cli" | ||
| version = "0.1.0" | ||
| edition = "2024" | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| fn main() { | ||
| println!("Hello, world!"); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,3 +4,8 @@ version = "0.1.0" | |
| edition = "2024" | ||
|
|
||
| [dependencies] | ||
| chrono = { version = "0.4", features = ["serde"] } | ||
| serde = { version = "1", features = ["derive"] } | ||
| serde_yml = "0.0.12" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Latest serde_yml version on crates.io
curl -s https://crates.io/api/v1/crates/serde_yml | jq '.crate.max_stable_version, .crate.newest_version'
# Security advisories for serde_yml
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: RUST, package: "serde_yml") {
nodes {
advisory { summary severity publishedAt }
vulnerableVersionRange
firstPatchedVersion { identifier }
}
}
}'Repository: FrilLab/frilvault Length of output: 313 Address the GitHub reports 🤖 Prompt for AI Agents |
||
| thiserror = "2" | ||
| uuid = { version = "1", features = ["v4", "serde"] } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| pub const VAULT_DIR_NAME: &str = ".vault"; | ||
| pub const NOTE_FILE_EXTENSION: &str = "yml"; | ||
| pub const SCHEMA_VERSION: u32 = 1; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| use thiserror::Error; | ||
|
|
||
| #[derive(Debug, Error)] | ||
| pub enum FrilVaultError { | ||
| #[error("io error: {0}")] | ||
| Io(#[from] std::io::Error), | ||
|
|
||
| #[error("yaml error: {0}")] | ||
| Yaml(#[from] serde_yml::Error), | ||
|
|
||
| #[error("source file path is outside workspace root")] | ||
| SourcePathOutsideWorkspace, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| mod errors; | ||
|
|
||
| pub use errors::FrilVaultError; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| pub fn add(left: u64, right: u64) -> u64 { | ||
| left + right | ||
| } | ||
| pub mod constants; | ||
| pub mod error; | ||
| pub mod note; | ||
| pub mod parser; | ||
| pub mod storage; | ||
| pub mod workspace; | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| pub use error::FrilVaultError; | ||
| pub use note::{AddNoteInput, Note, NoteFile, NoteService}; | ||
| pub use storage::YamlNoteRepository; | ||
| pub use workspace::{PathResolver, Workspace}; | ||
|
|
||
| pub type FrilVaultResult<T> = Result<T, FrilVaultError>; | ||
|
|
||
| #[test] | ||
| fn it_works() { | ||
| let result = add(2, 2); | ||
| assert_eq!(result, 4); | ||
| } | ||
| } | ||
| #[cfg(test)] | ||
| mod tests; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| use chrono::{DateTime, Utc}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use std::path::PathBuf; | ||
| use uuid::Uuid; | ||
|
|
||
| // TODO: Regex parser | ||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
| #[serde(tag = "type")] | ||
| pub enum NoteAnchor { | ||
| Line(LineAnchor), | ||
| Symbol(SymbolAnchor), | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub struct LineAnchor { | ||
| pub line: u32, | ||
| pub column: u32, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub struct SymbolAnchor { | ||
| pub name: String, | ||
| pub kind: SymbolKind, | ||
| pub signature: Option<String>, | ||
| pub line_hint: Option<u32>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub enum SymbolKind { | ||
| Function, | ||
| Struct, | ||
| Enum, | ||
| Trait, | ||
| Impl, | ||
| Method, | ||
| Unknown, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub struct Note { | ||
| /// id | ||
| pub id: Uuid, | ||
|
|
||
| /// source_file | ||
| pub source_file: PathBuf, | ||
|
|
||
| /// anchor: the position where the note is anchored. it can be either line-based or symbol-based. | ||
| pub anchor: NoteAnchor, | ||
|
|
||
| /// content: the content of the note. | ||
| pub content: String, | ||
|
|
||
| /// created_at: the time when the note was created. | ||
| pub created_at: DateTime<Utc>, | ||
|
|
||
| /// updated_at: the time when the note was last updated. initially same as created_at. | ||
| pub updated_at: DateTime<Utc>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| pub struct AddNoteInput { | ||
| /// source_file: the path to the source file where the note is located. | ||
| pub source_file: PathBuf, | ||
|
|
||
| /// anchor: the position where the note is anchored. it can be either line-based or symbol-based. | ||
| pub anchor: NoteAnchor, | ||
|
|
||
| /// content: the content of the note to be saved. | ||
| pub content: String, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] | ||
| pub struct NoteFile { | ||
| pub notes: Vec<Note>, | ||
| } | ||
|
|
||
| impl Note { | ||
| pub fn new(input: AddNoteInput) -> Self { | ||
| let now = Utc::now(); | ||
|
|
||
| Self { | ||
| id: Uuid::new_v4(), | ||
| source_file: input.source_file, | ||
| anchor: input.anchor, | ||
| content: input.content, | ||
| created_at: now, | ||
| updated_at: now, | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| mod entity; | ||
| mod service; | ||
|
|
||
| pub use entity::*; | ||
| pub use service::*; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| use std::path::Path; | ||
|
|
||
| use crate::{ | ||
| FrilVaultResult, | ||
| note::{AddNoteInput, Note}, | ||
| storage::YamlNoteRepository, | ||
| }; | ||
|
|
||
| pub struct NoteService { | ||
| repository: YamlNoteRepository, | ||
| } | ||
|
|
||
| impl NoteService { | ||
| pub fn new(repository: YamlNoteRepository) -> Self { | ||
| Self { repository } | ||
| } | ||
|
|
||
| pub fn add_note(&self, input: AddNoteInput) -> FrilVaultResult<Note> { | ||
| let note = Note::new(input); | ||
|
|
||
| self.repository.append_note(¬e)?; | ||
|
|
||
| Ok(note) | ||
| } | ||
|
|
||
| pub fn list_notes(&self, source_file: impl AsRef<Path>) -> FrilVaultResult<Vec<Note>> { | ||
| let note_file = self.repository.load_by_source_file(source_file.as_ref())?; | ||
|
|
||
| Ok(note_file.notes) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pub mod note_parser; | ||
| pub mod yaml_parser; | ||
|
|
||
| pub use note_parser::NoteParser; | ||
| pub use yaml_parser::YamlParser; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| use crate::FrilVaultResult; | ||
| use crate::note::NoteFile; | ||
|
|
||
| pub trait NoteParser { | ||
| fn serialize(&self, note_file: &NoteFile) -> FrilVaultResult<String>; | ||
|
|
||
| fn deserialize(&self, content: &str) -> FrilVaultResult<NoteFile>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| use crate::FrilVaultResult; | ||
| use crate::note::NoteFile; | ||
| use crate::parser::NoteParser; | ||
|
|
||
| #[derive(Debug, Default, Clone)] | ||
| pub struct YamlParser; | ||
|
|
||
| impl NoteParser for YamlParser { | ||
| fn serialize(&self, note_file: &NoteFile) -> FrilVaultResult<String> { | ||
| Ok(serde_yml::to_string(note_file)?) | ||
| } | ||
|
|
||
| fn deserialize(&self, content: &str) -> FrilVaultResult<NoteFile> { | ||
| Ok(serde_yml::from_str(content)?) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| mod yaml_repository; | ||
|
|
||
| pub use yaml_repository::*; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| use crate::FrilVaultResult; | ||
| use crate::note::{Note, NoteFile}; | ||
| use crate::parser::{NoteParser, YamlParser}; | ||
| use crate::workspace::PathResolver; | ||
| use std::fs; | ||
| use std::path::{Path, PathBuf}; | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| pub struct YamlNoteRepository { | ||
| path_resolver: PathResolver, | ||
| parser: YamlParser, | ||
| } | ||
|
|
||
| impl YamlNoteRepository { | ||
| // Create a YamlNoteRepository with the given PathResolver. | ||
| pub fn new(path_resolver: PathResolver) -> Self { | ||
| Self { | ||
| path_resolver, | ||
| parser: YamlParser, | ||
| } | ||
| } | ||
|
|
||
| // Read the existing note file, append the new note, and save it again. | ||
| pub fn append_note(&self, note: &Note) -> FrilVaultResult<()> { | ||
| let mut note_file = self.load_by_source_file(¬e.source_file)?; | ||
|
|
||
| note_file.notes.push(note.clone()); | ||
|
|
||
| self.save_by_source_file(¬e.source_file, ¬e_file)?; | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| // If the file does not exist, it returns an empty NoteFile. | ||
| pub fn load_by_source_file(&self, source_file: &Path) -> FrilVaultResult<NoteFile> { | ||
| let note_path = self.path_resolver.resolve_note_path(source_file); | ||
|
|
||
| self.load_by_note_path(note_path) | ||
| } | ||
|
|
||
| pub fn save_by_source_file( | ||
| &self, | ||
| source_file: &Path, | ||
| note_file: &NoteFile, | ||
| ) -> FrilVaultResult<()> { | ||
| let note_path = self.path_resolver.resolve_note_path(source_file); | ||
|
|
||
| if let Some(parent) = note_path.parent() { | ||
| fs::create_dir_all(parent)?; | ||
| } | ||
|
|
||
| let yaml = self.parser.serialize(note_file)?; | ||
|
|
||
| fs::write(note_path, yaml)?; | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| fn load_by_note_path(&self, note_path: PathBuf) -> FrilVaultResult<NoteFile> { | ||
| if !note_path.exists() { | ||
| return Ok(NoteFile::default()); | ||
| } | ||
|
|
||
| let content = fs::read_to_string(note_path)?; | ||
| let note_file = self.parser.deserialize(&content)?; | ||
|
|
||
| Ok(note_file) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| #[cfg(test)] | ||
| mod note_entity_test; | ||
|
|
||
| #[cfg(test)] | ||
| mod note_resolver_test; | ||
|
|
||
| #[cfg(test)] | ||
| mod note_service_test; | ||
|
|
||
| #[cfg(test)] | ||
| mod yaml_parser_test; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| use crate::{ | ||
| AddNoteInput, | ||
| note::{LineAnchor, Note, NoteAnchor}, | ||
| }; | ||
|
|
||
| #[test] | ||
| fn create_note_from_input() { | ||
| let input = AddNoteInput { | ||
| source_file: "src/main.rs".into(), | ||
| anchor: NoteAnchor::Line(LineAnchor { | ||
| line: 10, | ||
| column: 5, | ||
| }), | ||
| content: "test note".to_string(), | ||
| }; | ||
|
|
||
| let note = Note::new(input); | ||
|
|
||
| assert_eq!(note.content, "test note"); | ||
|
|
||
| match note.anchor { | ||
| NoteAnchor::Line(anchor) => { | ||
| assert_eq!(anchor.line, 10); | ||
| assert_eq!(anchor.column, 5); | ||
| } | ||
| _ => panic!("expected line anchor"), | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn create_note_generates_uuid() { | ||
| let input = AddNoteInput { | ||
| source_file: "src/main.rs".into(), | ||
| anchor: NoteAnchor::Line(LineAnchor { line: 1, column: 1 }), | ||
| content: "uuid test".to_string(), | ||
| }; | ||
|
|
||
| let note = Note::new(input); | ||
|
|
||
| assert_ne!(note.id.to_string(), ""); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| use crate::PathResolver; | ||
|
|
||
| #[test] | ||
| fn resolve_note_path_returns_vault_path() { | ||
| let resolver = PathResolver::new("/workspace"); | ||
|
|
||
| let path = resolver.resolve_note_path("src/main.rs"); | ||
|
|
||
| assert!(path.ends_with(".vault/src/main.rs.yml")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn workspace_relative_path() { | ||
| let resolver = PathResolver::new("/workspace"); | ||
|
|
||
| let relative = resolver | ||
| .to_workspace_relative("/workspace/src/main.rs") | ||
| .unwrap(); | ||
|
|
||
| assert_eq!(relative.to_string_lossy(), "src/main.rs"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likely typo in crate directory name:
frivault-clivs projectfrilvault.The CLI member is
apps/frivault-cli(missing anl), while the core crate isfrilvault-coreand the project is FrilVault. If unintentional, rename the directory/crate now while it's still a scaffold to avoid churn later.✏️ Proposed fix
Note: also rename the
apps/frivault-clidirectory and update itsCargo.tomlpackage name accordingly.🤖 Prompt for AI Agents