Created
October 3, 2025 12:57
-
-
Save st3fan/779ca952fb41e954d71be74bba6e46ec to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use std::sync::Mutex; | |
| use actix_web::{delete, get, post, put, web, App, HttpResponse, HttpServer, Result}; | |
| use rusqlite::{Connection, OptionalExtension}; | |
| use serde::{Deserialize, Serialize}; | |
| use utoipa::{OpenApi, ToSchema}; | |
| use utoipa_swagger_ui::SwaggerUi; | |
| #[derive(Serialize, ToSchema)] | |
| struct Thing { | |
| id: i32, | |
| name: String, | |
| } | |
| fn init_db(conn: &Connection) -> rusqlite::Result<()>{ | |
| conn.execute("CREATE TABLE IF NOT EXISTS things (id INTEGER PRIMARY KEY, name TEXT NOT NULL)", [])?; | |
| Ok(()) | |
| } | |
| #[utoipa::path( | |
| get, | |
| responses( | |
| (status = 200, description = "Things found successfully", body = Vec<Thing>), | |
| ), | |
| )] | |
| #[get("/things")] | |
| async fn list_things(app_state: web::Data<AppState>) -> Result<HttpResponse> { | |
| let conn = app_state.db.lock().unwrap(); | |
| let mut stmt = conn | |
| .prepare("SELECT id, name FROM things ORDER BY id") | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; | |
| let things = stmt.query_map([], |row| { | |
| Ok(Thing{id:row.get(0)?, name: row.get(1)?}) | |
| }) | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))? | |
| .map(|row| row.unwrap()) | |
| .collect::<Vec<Thing>>(); | |
| Ok(HttpResponse::Ok().json(things)) | |
| } | |
| #[utoipa::path( | |
| get, | |
| responses( | |
| (status = 200, description = "Thing found successfully", body = Thing), | |
| (status = NOT_FOUND, description = "Thing was not found") | |
| ), | |
| params( | |
| ("id" = u64, Path, description = "Thing database id to get Thing for"), | |
| ) | |
| )] | |
| #[get("/things/{id}")] | |
| async fn read_thing(app_state: web::Data<AppState>, id: web::Path<i32>) -> Result<HttpResponse> { | |
| let conn = app_state.db.lock().unwrap(); | |
| let mut stmt = conn | |
| .prepare("SELECT id, name FROM things WHERE id = ?") | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; | |
| let thing = stmt.query_row([id.into_inner()], |row| { | |
| Ok(Thing { | |
| id: row.get(0)?, | |
| name: row.get(1)?, | |
| }) | |
| }) | |
| .optional() | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))? | |
| .ok_or(actix_web::error::ErrorNotFound("Not Found"))?; | |
| Ok(HttpResponse::Ok().json(thing)) | |
| } | |
| #[utoipa::path( | |
| delete, | |
| responses( | |
| (status = 200, description = "Thing found and deleted successfully", body = Thing), | |
| (status = NOT_FOUND, description = "Thing was not found") | |
| ), | |
| params( | |
| ("id" = u64, Path, description = "Thing database id to get Thing for"), | |
| ) | |
| )] | |
| #[delete("/things/{id}")] | |
| async fn delete_thing(app_state: web::Data<AppState>, id: web::Path<i32>) -> Result<HttpResponse> { | |
| let conn = app_state.db.lock().unwrap(); | |
| let row_count = conn.execute("DELETE FROM things WHERE id = ?", [*id]) | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; | |
| if row_count == 0 { | |
| Ok(HttpResponse::NotFound().finish()) | |
| } else { | |
| Ok(HttpResponse::Ok().finish()) | |
| } | |
| } | |
| #[derive(Deserialize, Debug, ToSchema)] | |
| struct CreateThingRequest { | |
| name: String, | |
| } | |
| #[utoipa::path( | |
| post, | |
| request_body = CreateThingRequest, | |
| responses( | |
| (status = 200, description = "Thing created successfully", body = Thing), | |
| ), | |
| )] | |
| #[post("/things")] | |
| async fn create_thing(app_state: web::Data<AppState>, thing_dao: web::Json<CreateThingRequest>) -> Result<HttpResponse> { | |
| let conn = app_state.db.lock().unwrap(); | |
| let mut stmt = conn | |
| .prepare("INSERT INTO things (name) VALUES (?) RETURNING id, name") | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; | |
| let thing = stmt.query_row([thing_dao.into_inner().name], |row| { | |
| Ok(Thing { | |
| id: row.get(0)?, | |
| name: row.get(1)?, | |
| }) | |
| }) | |
| .optional() | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))? | |
| .ok_or(actix_web::error::ErrorNotFound("Not Found"))?; | |
| Ok(HttpResponse::Ok().json(thing)) | |
| } | |
| #[derive(Deserialize, Debug, ToSchema)] | |
| struct UpdateThingRequest { | |
| name: String, | |
| } | |
| #[utoipa::path( | |
| put, | |
| request_body = UpdateThingRequest, | |
| responses( | |
| (status = 200, description = "Thing found and deleted successfully", body = Thing), | |
| (status = NOT_FOUND, description = "Thing was not found") | |
| ), | |
| params( | |
| ("id" = u64, Path, description = "Thing database id to get Thing for"), | |
| ) | |
| )] | |
| #[put("/things/{id}")] | |
| async fn update_thing(app_state: web::Data<AppState>, id: web::Path<i32>, thing_dao: web::Json<UpdateThingRequest>) -> Result<HttpResponse> { | |
| let conn = app_state.db.lock().unwrap(); | |
| let mut stmt = conn | |
| .prepare("UPDATE things set NAME = ? WHERE id = ? returning id, name") | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))?; | |
| let thing = stmt.query_row((thing_dao.into_inner().name, id.into_inner()), |row| { | |
| Ok(Thing { | |
| id: row.get(0)?, | |
| name: row.get(1)?, | |
| }) | |
| }) | |
| .optional() | |
| .map_err(|e| actix_web::error::ErrorInternalServerError(e))? | |
| .ok_or(actix_web::error::ErrorNotFound("Not Found"))?; | |
| Ok(HttpResponse::Ok().json(thing)) | |
| } | |
| #[derive(OpenApi)] | |
| #[openapi( | |
| paths( | |
| create_thing, | |
| read_thing, | |
| update_thing, | |
| delete_thing, | |
| list_things, | |
| ), | |
| components( | |
| schemas(Thing) | |
| ), | |
| tags( | |
| (name = "things", description = "Thing management endpoints") | |
| ), | |
| info( | |
| title = "Things API", | |
| version = "1.0.0", | |
| description = "API Description" | |
| ) | |
| )] | |
| struct ApiDoc; | |
| struct AppState { | |
| db: Mutex<Connection>, | |
| } | |
| #[actix_web::main] | |
| async fn main() -> std::io::Result<()> { | |
| let conn = Connection::open("test.db").expect("Failed to open database"); | |
| init_db(&conn).expect("Failed to initialize database"); | |
| let app_state = web::Data::new(AppState {db: Mutex::new(conn)}); | |
| println!("Starting server at http://127.0.0.1:8080"); | |
| HttpServer::new(move || { | |
| App::new() | |
| .app_data(app_state.clone()) | |
| .service(create_thing) | |
| .service(read_thing) | |
| .service(update_thing) | |
| .service(delete_thing) | |
| .service(list_things) | |
| .service( | |
| SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-docs/openapi.json", ApiDoc::openapi()) | |
| ) | |
| }) | |
| .bind(("127.0.0.1", 8080))? | |
| .run() | |
| .await | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment