Skip to content

Instantly share code, notes, and snippets.

@st3fan
Created October 3, 2025 12:57
Show Gist options
  • Select an option

  • Save st3fan/779ca952fb41e954d71be74bba6e46ec to your computer and use it in GitHub Desktop.

Select an option

Save st3fan/779ca952fb41e954d71be74bba6e46ec to your computer and use it in GitHub Desktop.
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