Skip to content

Commit

Permalink
#5 cli args support (#45)
Browse files Browse the repository at this point in the history
Co-authored-by: Przemyslaw Hugh Kaznowski <[email protected]>
  • Loading branch information
seun-ja and phughk authored Mar 2, 2024
1 parent e0dbe8a commit 0e5daee
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 15 deletions.
21 changes: 21 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,27 @@ cargo build --release
RUST_LOG=laerning_tool=trace,surrealdb=debug cargo run
```

The project also supports CLI commands. You can run them
```
./target/debug/laerning-tool-api [COMMAND]
```

Here's an example:

```
./target/debug/laerning-tool-api -h
This is a software project to help people make flash cards to test themselves on things they want to learn.

Usage: laerning-tool-api [OPTIONS]

Options:
-b, --bind <BIND> Socket address on which the server will listen
--db-location <DB_LOCATION> SurrealDB Storage Location. Should be in URI format (ie file://)
--directory <DIRECTORY> Dataset Directory location
-h, --help Print help
-V, --version Print version
```

You can find pre-made releases in the https://github.com/phughk/laerning-tool/releases[releases tab] to the right of the GitHub page.

=== How do I run the binary on Mac
Expand Down
1 change: 1 addition & 0 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ path = "src/main.rs"
async-trait = "0.1.73"
axum = "0.6.18"
axum-test = "14.2.2"
clap = {version = "4.4.2", features = ["derive"]}
env_logger = "0.10.0"
hyper = { version = "1.1.0", features = ["http1"] }
log = "0.4.19"
Expand Down
23 changes: 23 additions & 0 deletions engine/src/api/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use clap::Parser;

/// Laerning Tool CLI
#[derive(Parser, Debug)]
#[command(
author = "Laerning Tool API",
version = "1.0",
about = "This is a software project to help people make flash cards to test themselves on things they want to learn." ,
long_about = None
)]
pub struct ToolArgs {
/// Socket address on which the server will listen.
#[arg(short, long)]
pub bind: Option<String>,

/// SurrealDB Storage Location. Should be in URI format (ie file://)
#[arg(long)]
pub db_location: Option<String>,

/// Dataset Directory location
#[arg(long)]
pub directory: Option<String>,
}
2 changes: 1 addition & 1 deletion engine/src/api/game/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub async fn game_list(
State(state): State<Arc<ApiState>>,
) -> Result<Json<Vec<GameListing>>, GameListingErrorResponse> {
let state = state.clone();
let games = state.clone().repository.create_list().await.map_err(|e| {
let games = state.clone().repository.create_list().await.map_err(|_e| {
Into::<GameListingErrorResponse>::into(GameListingError::InternalError {
cause: "Unexpected internal error".to_string(),
})
Expand Down
1 change: 1 addition & 0 deletions engine/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod cli;
mod dataset;
mod game;
mod test;
Expand Down
2 changes: 1 addition & 1 deletion engine/src/api/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mod test {

#[tokio::test]
async fn test_games_can_be_created() {
let db = crate::start_db().await;
let db = crate::start_db(None).await;
let repo = LaerningToolRepository::new(db);
let api = api::new(repo);
let app = api.make_server().await;
Expand Down
47 changes: 37 additions & 10 deletions engine/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,30 @@ mod api;
mod repository;
mod xml;

use crate::xml::error::Error;
use crate::xml::LearningModule;

use api::cli::ToolArgs;
use clap::Parser;
use hyper::Server;
use std::net::SocketAddr;
use surrealdb::engine::local::{Db, Mem};
use std::str::FromStr;
use surrealdb::engine::local::{Db, File, Mem};

use crate::repository::dataset::Dataset;
use crate::repository::{LaerningToolRepository, Repository};
use surrealdb::Surreal;

async fn load_data() -> Vec<LearningModule> {
xml::list_modules("engine/data").unwrap()
async fn load_data(directory: &str) -> Vec<LearningModule> {
xml::list_modules(directory).unwrap()
}

async fn start_db() -> Surreal<Db> {
let db: Surreal<Db> = Surreal::new::<Mem>(()).await.unwrap();
async fn start_db(addr: Option<String>) -> Surreal<Db> {
let db: Surreal<Db> = if let Some(address) = addr {
Surreal::new::<File>(&*address).await.unwrap()
} else {
Surreal::new::<Mem>(()).await.unwrap()
};

// Auth not supported in memory
/*
Expand All @@ -35,25 +43,40 @@ async fn start_db() -> Surreal<Db> {
db
}

async fn start_server(repository: LaerningToolRepository) {
async fn start_server(repository: LaerningToolRepository, socket_addr: Option<String>) -> Result<(), Error> {
// Create a new Axum router
let api_state = api::new(repository);
let app = api_state.make_server().await;

// Define the address on which the server will listen
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let addr = if let Some(address) = socket_addr {
SocketAddr::from_str(&address)
.map_or(SocketAddr::from(([127, 0, 0, 1], 3000)), |address| address)
} else {
SocketAddr::from(([127, 0, 0, 1], 3000))
};

// Start the server
println!("Server running on http://{}", addr);
println!("Swagger UI available at: http://{}/swagger-ui/#/", addr);
Server::bind(&addr).serve(app).await.unwrap();

Ok(())
}

#[tokio::main]
async fn main() {
env_logger::init();
let data = load_data().await;
let db = start_db().await;
let args = ToolArgs::parse();

let data = if let Some(directory) = args.directory {
load_data(&directory).await
} else {
Vec::<LearningModule>::new()
};

let db = start_db(args.db_location).await;

let repository = LaerningToolRepository::new(db);
repository
.create_batch_datasets(
Expand All @@ -63,5 +86,9 @@ async fn main() {
)
.await
.unwrap();
start_server(repository).await;

start_server(repository, args.bind)
.await
.map_err(|e| Error::ServerError(e.to_string()))
.unwrap();
}
5 changes: 4 additions & 1 deletion engine/src/repository/dataset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ pub enum DatasetError {

#[async_trait]
impl Repository<Dataset, DatasetError> for LaerningToolRepository {
async fn create_dataset(&self, dataset: Dataset) -> Result<Dataset, RepositoryError<DatasetError>> {
async fn create_dataset(
&self,
dataset: Dataset,
) -> Result<Dataset, RepositoryError<DatasetError>> {
let mut bindings: HashMap<String, Value> = HashMap::new();
bindings.insert("data".to_string(), Value::Object(dataset.into()));

Expand Down
5 changes: 4 additions & 1 deletion engine/src/repository/game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ impl Repository<Game, GameError> for LaerningToolRepository {
Ok(data.unwrap())
}

async fn create_batch_datasets(&self, datasets: Vec<Game>) -> Result<(), RepositoryError<GameError>> {
async fn create_batch_datasets(
&self,
datasets: Vec<Game>,
) -> Result<(), RepositoryError<GameError>> {
for dataset in datasets {
self.create_dataset(dataset).await.unwrap();
}
Expand Down
2 changes: 2 additions & 0 deletions engine/src/xml/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub enum Error {
Io {},
ListModule { error: String, path: String },
Serde {},
ServerError(String),
}

impl Display for Error {
Expand All @@ -18,6 +19,7 @@ impl Display for Error {
error, path
),
Error::Serde {} => write!(f, "Serde serialization/deserialization error"),
Error::ServerError(error) => write!(f, "Server unable to start: {error}"),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion engine/src/xml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl<'de> Visitor<'de> for VersionVisitor {
TODO: #38 you should not implement `visit_string` without also implementing `visit_str`,
hence `visit_str` has to be implemented
*/
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
fn visit_str<E>(self, _v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Expand Down

0 comments on commit 0e5daee

Please sign in to comment.