Compare commits

...

5 Commits

  1. 14
      apps/app_api/src/api.rs
  2. 78
      apps/app_api/src/api/dictionary.rs
  3. 74
      apps/app_api/src/api/health.rs
  4. 119
      apps/app_api/src/api/major_pl.rs
  5. 2
      apps/app_api/src/commands/listen.rs
  6. 53
      apps/app_api/src/container.rs
  7. 2
      apps/app_api/src/main.rs
  8. 22
      apps/app_api/src/router.rs
  9. 25
      apps/app_api/src/router/handlers.rs
  10. 34
      apps/app_api/src/router/responses.rs
  11. 16
      apps/app_api/src/state.rs
  12. 5
      apps/app_cli/src/commands.rs
  13. 77
      apps/app_cli/src/commands/list_dicts.rs
  14. 3
      apps/app_cli/src/config.rs
  15. 3
      config.toml
  16. 1
      lib/src/common.rs
  17. 42
      lib/src/common/entities.rs
  18. 32
      lib/src/common/traits.rs
  19. 74
      lib/src/dictionary.rs
  20. 2
      lib/src/dictionary/dict_importer.rs
  21. 4
      lib/src/dictionary/infrastructure/json_file_dict_source.rs
  22. 33
      lib/src/dictionary/infrastructure/sqlite_dict_repository.rs
  23. 0
      lib/src/dictionary/service.rs
  24. 4
      lib/src/lib.rs

14
apps/app_api/src/api.rs

@ -0,0 +1,14 @@
use crate::state::AppState;
use axum::Router;
use std::sync::Arc;
pub mod dictionary;
pub mod health;
pub mod major_pl;
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.nest("/api", health::routes())
.nest("/api", dictionary::routes())
.nest("/api", major_pl::routes())
}

78
apps/app_api/src/api/dictionary.rs

@ -0,0 +1,78 @@
use axum::{Json, Router, extract::State, http::StatusCode, response::IntoResponse, routing::get};
use serde::Serialize;
use std::sync::Arc;
use crate::state::AppState;
// --- DTOs ---
#[derive(Debug, Serialize)]
pub struct DictListResponse {
pub dictionaries: Vec<DictListEntryResponse>,
}
#[derive(Debug, Serialize)]
pub struct DictListEntryResponse {
pub name: String,
pub entry_count: u64,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
}
}
impl From<anyhow::Error> for ErrorResponse {
fn from(err: anyhow::Error) -> Self {
Self {
error: err.to_string(),
}
}
}
impl From<applib::RepositoryError> for ErrorResponse {
fn from(err: applib::RepositoryError) -> Self {
Self {
error: err.to_string(),
}
}
}
// --- Handlers ---
pub async fn list_dicts_handler(
State(state): State<Arc<AppState>>,
) -> Result<Json<DictListResponse>, ErrorResponse> {
let default_repo = state.container.create_dict_repo("default").await?;
let dict_names = default_repo.fetch_dicts().await?;
let mut entries = Vec::with_capacity(dict_names.len());
for dict_name in dict_names {
let dict_repo = state.container.create_dict_repo(&dict_name).await?;
let entry_count = dict_repo.count_entries().await?;
entries.push(DictListEntryResponse {
name: dict_name,
entry_count,
});
}
Ok(Json(DictListResponse {
dictionaries: entries,
}))
}
// --- Router ---
pub fn routes() -> Router<Arc<AppState>> {
Router::new().route("/dicts", get(list_dicts_handler))
}

74
apps/app_api/src/api/health.rs

@ -0,0 +1,74 @@
use axum::{
Json, Router,
extract::State,
http::StatusCode,
response::IntoResponse,
routing::{get, post},
};
use chrono::Utc;
use serde::Serialize;
use serde_json::Value;
use std::sync::Arc;
use crate::state::AppState;
// --- DTOs ---
#[derive(Debug, Serialize)]
pub struct EchoResponse {
pub data: Value,
pub timestamp: String,
}
#[derive(Debug, Serialize)]
pub struct VersionResponse {
pub name: String,
pub version: String,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
}
}
impl<E: std::error::Error> From<E> for ErrorResponse {
fn from(err: E) -> Self {
Self {
error: err.to_string(),
}
}
}
// --- Handlers ---
pub async fn echo_handler(
State(_state): State<Arc<AppState>>,
Json(payload): Json<Value>,
) -> Result<Json<EchoResponse>, ErrorResponse> {
let response = EchoResponse {
data: payload,
timestamp: Utc::now().to_rfc3339(),
};
Ok(Json(response))
}
pub async fn version_handler(State(state): State<Arc<AppState>>) -> Json<VersionResponse> {
Json(VersionResponse {
name: state.name.clone(),
version: state.version.clone(),
})
}
// --- Router ---
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.route("/echo", post(echo_handler))
.route("/version", get(version_handler))
}

119
apps/app_api/src/api/major_pl.rs

@ -0,0 +1,119 @@
use axum::{
Json, Router,
extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
routing::get,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::state::AppState;
#[derive(Debug, Deserialize)]
pub struct EncodeQuery {
pub dict: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct DecodeQuery {
pub dict: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct EncodeResponse {
pub input: String,
pub dict: String,
pub result: Vec<Vec<EncodePart>>,
}
#[derive(Debug, Serialize)]
pub struct EncodePart {
pub value: u64,
pub words: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct DecodeResponse {
pub input: String,
pub result: String,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
}
}
impl From<anyhow::Error> for ErrorResponse {
fn from(err: anyhow::Error) -> Self {
Self {
error: err.to_string(),
}
}
}
pub async fn encode_handler(
State(state): State<Arc<AppState>>,
Path(input): Path<String>,
Query(params): Query<EncodeQuery>,
) -> Result<Json<EncodeResponse>, ErrorResponse> {
let dict_name = params.dict.unwrap_or_else(|| "demo_pl".to_string());
let encoder = state
.container
.create_encoder(&dict_name)
.await
.map_err(|e| anyhow::anyhow!("Failed to create encoder: {}", e))?;
let result = encoder
.encode(&input)
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
let encoded_parts: Vec<Vec<EncodePart>> = result
.iter()
.map(|split| {
split
.iter()
.map(|part| EncodePart {
value: part.value,
words: part.words.clone(),
})
.collect()
})
.collect();
Ok(Json(EncodeResponse {
input,
dict: dict_name,
result: encoded_parts,
}))
}
pub async fn decode_handler(
State(state): State<Arc<AppState>>,
Path(input): Path<String>,
Query(_params): Query<DecodeQuery>,
) -> Result<Json<DecodeResponse>, ErrorResponse> {
let decoder = state
.container
.create_decoder()
.map_err(|e| anyhow::anyhow!("Failed to create decoder: {}", e))?;
let result = decoder
.decode(&input)
.map_err(|e| anyhow::anyhow!("Failed to decode: {}", e))?;
Ok(Json(DecodeResponse {
input,
result: result.as_str().to_string(),
}))
}
pub fn routes() -> Router<Arc<AppState>> {
Router::new()
.route("/encode/major_pl/{input}", get(encode_handler))
.route("/decode/major_pl/{input}", get(decode_handler))
}

2
apps/app_api/src/commands/listen.rs

@ -61,7 +61,7 @@ impl Executable for ListenCmd {
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Listen config missing"))?;
let app = router::create_router();
let app = router::create_router().await?;
let addr = format!("{}:{}", listen_config.host, listen_config.port);
let listener = TcpListener::bind(&addr).await?;

53
apps/app_api/src/container.rs

@ -1,11 +1,11 @@
// use std::sync::Arc;
use std::sync::Arc;
// use applib::DictImporter;
// use applib::DictRepository;
// use applib::SqliteDictRepository;
// use applib::SystemDecoder;
// use applib::SystemEncoder;
// use applib::sys_major as major;
use applib::DictImporter;
use applib::DictRepository;
use applib::SqliteDictRepository;
use applib::SystemDecoder;
use applib::SystemEncoder;
use applib::sys_major as major;
#[derive(Clone)]
pub struct Container;
@ -15,17 +15,32 @@ impl Container {
Ok(Self)
}
// pub async fn create_dict_importer(&self, dict_name: &str) -> anyhow::Result<DictImporter> {
// let repo = self.create_dict_repo(dict_name).await?;
// Ok(DictImporter::new(repo))
// }
pub async fn create_dict_importer(&self, dict_name: &str) -> anyhow::Result<DictImporter> {
let repo = self.create_dict_repo(dict_name).await?;
Ok(DictImporter::new(repo))
}
pub async fn create_dict_repo(
&self,
dict_name: &str,
) -> anyhow::Result<Arc<dyn DictRepository>> {
let mut dict_repo = SqliteDictRepository::new("sqlite:app.db").await?;
dict_repo.use_dict(dict_name);
Ok(Arc::new(dict_repo))
}
// pub async fn create_dict_repo(
// &self,
// dict_name: &str,
// ) -> anyhow::Result<Arc<dyn DictRepository>> {
// let mut dict_repo = SqliteDictRepository::new("sqlite:app.db").await?;
// dict_repo.use_dict(dict_name);
// Ok(Arc::new(dict_repo))
// }
pub fn create_decoder(&self) -> anyhow::Result<Box<dyn SystemDecoder>> {
Ok(Box::new(major::Decoder::new(major::rules_pl::get_rules())))
}
pub async fn create_encoder(&self, dict_name: &str) -> anyhow::Result<Box<dyn SystemEncoder>> {
let dict = self.create_dict_repo(dict_name).await?;
let decoder = self.create_decoder()?;
let words_stream = dict.stream_batches(1000).await.unwrap();
let lvmap = major::LenValueMap::from_stream(words_stream, &(*decoder))
.await
.unwrap();
let encoder = major::Encoder::new(lvmap);
Ok(Box::new(encoder))
}
}

2
apps/app_api/src/main.rs

@ -1,8 +1,10 @@
mod api;
mod app;
mod commands;
mod config;
mod container;
mod router;
mod state;
use anyhow::Result;
use app::Application;

22
apps/app_api/src/router.rs

@ -1,23 +1,15 @@
use axum::{
Router,
routing::{get, post},
};
use axum::Router;
use std::sync::Arc;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
mod handlers;
mod responses;
mod state;
use crate::api;
use crate::state::AppState;
pub use state::AppState;
pub async fn create_router() -> anyhow::Result<Router> {
let state = Arc::new(AppState::new().await?);
pub fn create_router() -> Router {
let state = Arc::new(AppState::new());
Router::new()
.route("/api/echo", post(handlers::echo_handler))
.route("/api/version", get(handlers::version_handler))
Ok(api::routes()
.with_state(state)
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive())
.layer(CorsLayer::permissive()))
}

25
apps/app_api/src/router/handlers.rs

@ -1,25 +0,0 @@
use axum::{Json, extract::State};
use chrono::Utc;
use serde_json::Value;
use std::sync::Arc;
use super::responses::{EchoResponse, ErrorResponse, VersionResponse};
use super::state::AppState;
pub async fn echo_handler(
State(_state): State<Arc<AppState>>,
Json(payload): Json<Value>,
) -> Result<Json<EchoResponse>, ErrorResponse> {
let response = EchoResponse {
data: payload,
timestamp: Utc::now().to_rfc3339(),
};
Ok(Json(response))
}
pub async fn version_handler(State(state): State<Arc<AppState>>) -> Json<VersionResponse> {
Json(VersionResponse {
name: state.name.clone(),
version: state.version.clone(),
})
}

34
apps/app_api/src/router/responses.rs

@ -1,34 +0,0 @@
use axum::{Json, http::StatusCode, response::IntoResponse};
use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Serialize)]
pub struct EchoResponse {
pub data: Value,
pub timestamp: String,
}
#[derive(Debug, Serialize)]
pub struct VersionResponse {
pub name: String,
pub version: String,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
impl IntoResponse for ErrorResponse {
fn into_response(self) -> axum::response::Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
}
}
impl<E: std::error::Error> From<E> for ErrorResponse {
fn from(err: E) -> Self {
Self {
error: err.to_string(),
}
}
}

16
apps/app_api/src/router/state.rs → apps/app_api/src/state.rs

@ -1,23 +1,21 @@
pub const APP_NAME: &str = env!("CARGO_PKG_NAME");
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
use crate::container::Container;
#[derive(Clone)]
pub struct AppState {
pub name: String,
pub version: String,
pub container: Container,
}
impl AppState {
pub fn new() -> Self {
Self {
pub async fn new() -> anyhow::Result<Self> {
Ok(Self {
name: APP_NAME.to_string(),
version: APP_VERSION.to_string(),
}
}
}
impl Default for AppState {
fn default() -> Self {
Self::new()
container: Container::new().await?,
})
}
}

5
apps/app_cli/src/commands.rs

@ -1,6 +1,7 @@
pub mod decode;
pub mod encode;
pub mod import_dict;
pub mod list_dicts;
use crate::config::AppConfig;
use crate::container::Container;
@ -22,6 +23,9 @@ pub enum Command {
/// Import dictionary
ImportDict(import_dict::ImportDictCmd),
/// List all dictionaries
ListDicts(list_dicts::ListDictsCmd),
}
impl Command {
@ -30,6 +34,7 @@ impl Command {
Command::Decode(cmd) => Box::new(cmd),
Command::Encode(cmd) => Box::new(cmd),
Command::ImportDict(cmd) => Box::new(cmd),
Command::ListDicts(cmd) => Box::new(cmd),
}
}
}

77
apps/app_cli/src/commands/list_dicts.rs

@ -0,0 +1,77 @@
use crate::commands::{ClapArgs, Configurable, Executable};
use crate::config::AppConfig;
use crate::container::Container;
use anyhow::Result;
use async_trait::async_trait;
use config::ConfigBuilder;
use config::builder::DefaultState;
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub show_counts: bool,
}
#[derive(ClapArgs, Debug, Clone)]
pub struct ListDictsCmd {
#[arg(short, long, help = defaults::HELP_LIST_DICTS_COUNTS)]
pub show_counts: bool,
}
impl Configurable for ListDictsCmd {
fn apply_defaults(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>> {
builder
.set_default("list_dicts.show_counts", defaults::SHOW_COUNTS)
.map_err(Into::into)
}
fn apply_overrides(
&self,
builder: ConfigBuilder<DefaultState>,
) -> Result<ConfigBuilder<DefaultState>> {
builder
.set_override("list_dicts.show_counts", self.show_counts)
.map_err(Into::into)
}
}
#[async_trait]
impl Executable for ListDictsCmd {
async fn execute(&self, config: &AppConfig, container: &Container) -> Result<()> {
let config = config
.list_dicts
.as_ref()
.expect("ListDicts config not set");
let repo = container.create_dict_repo("default").await?;
let dicts = repo.fetch_dicts().await?;
if dicts.is_empty() {
println!("No dictionaries found.");
return Ok(());
}
println!("Dictionaries:");
for dict_name in &dicts {
if config.show_counts {
let count_repo = container.create_dict_repo(dict_name).await?;
let count = count_repo.count_entries().await?;
println!(" - {} ({} entries)", dict_name, count);
} else {
println!(" - {}", dict_name);
}
}
Ok(())
}
}
mod defaults {
use const_format::formatcp;
pub const SHOW_COUNTS: bool = false;
pub const HELP_LIST_DICTS_COUNTS: &str = formatcp!("Show entry counts for each dictionary");
}

3
apps/app_cli/src/config.rs

@ -1,5 +1,4 @@
use crate::commands::*;
// use crate::commands::{Configurable, GlobalArgs};
use anyhow::{Context, Result};
use config::{Config, Environment, File};
use serde::Deserialize;
@ -12,6 +11,8 @@ pub struct AppConfig {
pub encode: Option<encode::Config>,
#[serde(default)]
pub import_dict: Option<import_dict::Config>,
#[serde(default)]
pub list_dicts: Option<list_dicts::Config>,
pub log_level: String,
}

3
config.toml

@ -1,4 +1 @@
log_level = "info"
[decode]
input = "CONFIGTEST"

1
lib/src/common.rs

@ -2,6 +2,5 @@ pub mod entities;
pub mod errors;
pub mod traits;
pub use self::traits::DictRepository;
pub use self::traits::SystemDecoder;
pub use self::traits::SystemEncoder;

42
lib/src/common/entities.rs

@ -2,7 +2,7 @@ use super::errors::CodecError;
use serde::Serialize;
use std::num::ParseIntError;
use std::ops::Deref;
use std::{collections::HashMap, u64};
use std::u64;
/// A number encoded as a sequence of words
#[derive(Debug, Clone, Serialize)]
@ -104,43 +104,3 @@ impl TryFrom<usize> for DecodedLength {
}
}
}
// --- Dictionary ---
pub type DictEntryId = u64;
#[derive(Debug, Clone, PartialEq)]
pub struct DictEntry {
pub id: Option<DictEntryId>,
pub text: String,
pub metadata: HashMap<String, String>,
}
impl DictEntry {
pub fn new(id: Option<DictEntryId>, text: String) -> Self {
DictEntry {
id,
text,
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Dict {
pub name: String,
pub entries: HashMap<DictEntryId, DictEntry>,
}
impl Dict {
pub fn new(name: String) -> Self {
Dict {
name,
entries: HashMap::new(),
}
}
pub fn add_entry(&mut self, entry: DictEntry) {
self.entries.insert(entry.id.unwrap(), entry);
}
}

32
lib/src/common/traits.rs

@ -1,8 +1,6 @@
use futures::stream::BoxStream;
use crate::common::{
entities::{DecodedValue, Dict, DictEntry, EncodedValue},
errors::{CodecError, RepositoryError},
entities::{DecodedValue, EncodedValue},
errors::CodecError,
};
pub trait SystemDecoder: Send + Sync {
@ -13,29 +11,3 @@ pub trait SystemEncoder: Send + Sync {
fn initialize(&self) -> Result<(), CodecError>;
fn encode(&self, word: &str) -> Result<EncodedValue, CodecError>;
}
#[async_trait::async_trait]
pub trait DictRepository: Send + Sync {
fn use_dict(&mut self, name: &str);
async fn create_dict(&self) -> Result<(), RepositoryError>;
/// "Upsert" logic:
/// - If entry exists (by text), update metadata.
/// - If not, insert new.
/// - IDs are handled by the Database.
async fn save_entries(&self, entries: &[DictEntry]) -> Result<(), RepositoryError>;
/// Fetch a page of entries.
async fn fetch_many(&self, limit: usize, offset: usize) -> Result<Dict, RepositoryError>;
/// Returns a cold stream that fetches strings in chunks.
/// The stream yields `Result<Vec<String>, RepositoryError>`.
async fn stream_batches(
&self,
batch_size: usize,
) -> Result<BoxStream<'_, Result<Vec<String>, RepositoryError>>, RepositoryError>;
}
pub trait DictSource {
fn next_entry(&mut self) -> Option<Result<DictEntry, anyhow::Error>>;
}

74
lib/src/dictionary.rs

@ -1,6 +1,80 @@
mod dict_importer;
mod infrastructure;
use futures::stream::BoxStream;
use crate::common::errors::RepositoryError;
pub use self::dict_importer::DictImporter;
pub use self::infrastructure::json_file_dict_source::JsonFileDictSource;
pub use self::infrastructure::sqlite_dict_repository::SqliteDictRepository;
use std::collections::HashMap;
pub type DictEntryId = u64;
#[derive(Debug, Clone, PartialEq)]
pub struct DictEntry {
pub id: Option<DictEntryId>,
pub text: String,
pub metadata: HashMap<String, String>,
}
impl DictEntry {
pub fn new(id: Option<DictEntryId>, text: String) -> Self {
DictEntry {
id,
text,
metadata: HashMap::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct Dict {
pub name: String,
pub entries: HashMap<DictEntryId, DictEntry>,
}
impl Dict {
pub fn new(name: String) -> Self {
Dict {
name,
entries: HashMap::new(),
}
}
pub fn add_entry(&mut self, entry: DictEntry) {
self.entries.insert(entry.id.unwrap(), entry);
}
}
#[async_trait::async_trait]
pub trait DictRepository: Send + Sync {
fn use_dict(&mut self, name: &str);
async fn create_dict(&self) -> Result<(), RepositoryError>;
async fn fetch_dicts(&self) -> Result<Vec<String>, RepositoryError>;
async fn count_entries(&self) -> Result<u64, RepositoryError>;
/// "Upsert" logic:
/// - If entry exists (by text), update metadata.
/// - If not, insert new.
/// - IDs are handled by the Database.
async fn save_entries(&self, entries: &[DictEntry]) -> Result<(), RepositoryError>;
/// Fetch a page of entries.
async fn fetch_many(&self, limit: usize, offset: usize) -> Result<Dict, RepositoryError>;
/// Returns a cold stream that fetches strings in chunks.
/// The stream yields `Result<Vec<String>, RepositoryError>`.
async fn stream_batches(
&self,
batch_size: usize,
) -> Result<BoxStream<'_, Result<Vec<String>, RepositoryError>>, RepositoryError>;
}
pub trait DictSource {
fn next_entry(&mut self) -> Option<Result<DictEntry, anyhow::Error>>;
}

2
lib/src/dictionary/dict_importer.rs

@ -1,6 +1,6 @@
use std::sync::Arc;
use crate::common::traits::{DictRepository, DictSource};
use super::{DictRepository, DictSource};
pub struct DictImporter {
repo: Arc<dyn DictRepository>,

4
lib/src/dictionary/infrastructure/json_file_dict_source.rs

@ -1,5 +1,5 @@
use crate::common::entities::DictEntry;
use crate::common::traits::DictSource;
use crate::dictionary::DictEntry;
use crate::dictionary::DictSource;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;

33
lib/src/dictionary/infrastructure/sqlite_dict_repository.rs

@ -1,6 +1,5 @@
use crate::common::entities::{Dict, DictEntry};
use crate::common::errors::RepositoryError;
use crate::common::traits::DictRepository;
use crate::dictionary::{Dict, DictEntry, DictRepository};
use futures::TryStreamExt;
use futures::stream::BoxStream;
@ -94,6 +93,10 @@ impl SqliteDictRepository {
#[async_trait::async_trait]
impl DictRepository for SqliteDictRepository {
fn use_dict(&mut self, name: &str) {
self.dict_name = name.to_string();
}
async fn create_dict(&self) -> Result<(), RepositoryError> {
sqlx::query("INSERT OR IGNORE INTO dictionaries (name) VALUES (?)")
.bind(&self.dict_name)
@ -103,8 +106,30 @@ impl DictRepository for SqliteDictRepository {
Ok(())
}
fn use_dict(&mut self, name: &str) {
self.dict_name = name.to_string();
async fn fetch_dicts(&self) -> Result<Vec<String>, RepositoryError> {
let rows = sqlx::query("SELECT name FROM dictionaries ORDER BY name")
.fetch_all(&self.pool)
.await
.map_err(|e| RepositoryError::StorageError(e.to_string()))?;
let dicts = rows.iter().map(|row| row.get("name")).collect();
Ok(dicts)
}
async fn count_entries(&self) -> Result<u64, RepositoryError> {
let dict_id = self.get_dict_id().await?;
let row = sqlx::query("SELECT COUNT(*) as count FROM entries WHERE dictionary_id = ?")
.bind(dict_id)
.fetch_optional(&self.pool)
.await
.map_err(|e| RepositoryError::StorageError(e.to_string()))?;
match row {
Some(r) => Ok(r.get::<i64, _>("count") as u64),
None => Ok(0),
}
}
async fn save_entries(&self, entries: &[DictEntry]) -> Result<(), RepositoryError> {

0
lib/src/dictionary/service.rs

4
lib/src/lib.rs

@ -12,9 +12,11 @@ mod common;
mod dictionary;
pub mod sys_major;
pub use self::common::DictRepository;
pub use self::common::SystemDecoder;
pub use self::common::SystemEncoder;
pub use self::common::errors::RepositoryError;
pub use self::dictionary::DictImporter;
pub use self::dictionary::DictRepository;
pub use self::dictionary::JsonFileDictSource;
pub use self::dictionary::SqliteDictRepository;

Loading…
Cancel
Save