I started implementing my first API with rust. It looks like Actix-web is a very popular framework, so I thought to give it a spin.
However, linking my data types with the handlers isn't working yet.
I assume there are several issues here, so please bear with me.
First my types:
#[derive(Serialize, Deserialize)]
pub struct Reservation {
pub id: i64,
pub pub_key: Vec<u8>,
pub expiry: u64,
pub terms: Vec<u8>,
pub sig: Vec<u8>,
pub range: Range,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Range {
pub id: u64,
pub expiry: Option<u64>,
pub terms: Option<Vec<u8>>,
pub slots: Vec<Slot>,
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Slot {
pub id: u64,
pub app_id: u64,
pub reserved: Option<Vec<u8>>,
pub consumed: bool,
pub expiry: Option<u64>,
pub terms: Option<Vec<u8>>,
}
I am already not sure how internally the deserialization of Vec<u8>
has to work. Also, another doubt is on Option
fields.
One of my handler looks like this:
pub async fn reserve(db: web::Data<ReserveDB>, item: web::Json<Reservation>) -> HttpResponse {
let reservation = item.into_inner();
match db.save_reservation(&reservation) {
// prepare response
}
}
The server is started like this:
#[actix_web::main]
pub async fn start(&self) -> std::io::Result<()> {
let settings = default_settings().expect("settings should have been set up");
let reserve_db = match ReserveDB::new(settings) {
// handle setting up db
};
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.app_data(web::Data::new(reserve_db.clone()))
.service(
web::scope("/api/v1")
.route("/reservation", web::put().to(handlers::reserve))
)
})
.bind("127.0.0.1:8080")?
.workers(2)
.run()
.await
}
So far, every attempt to call this via curl
failed with a deserialization error:
2025-05-11T22:45:32Z DEBUG actix_web::types::json] Failed to deserialize Json from payload. Request path: /api/v1/reservation
[2025-05-11T22:45:32Z DEBUG actix_web::middleware::logger] Error in response: ContentType
[2025-05-11T22:45:32Z INFO actix_web::middleware::logger] 127.0.0.1 "PUT /api/v1/reservation HTTP/1.1" 400 18 "-" "curl/8.13.0" 0.000238
As you can see it seems as that I am also responding incorrectly. But if that is not relevant to this question, let's leave that out for now. I want to understand why it is not able to deserialize json correctly.
Here is one of the attempts at calling the endpoint, with a fully expanded Reservation
struct:
curl -v -X PUT -d '{"id":-1,"pub_key":[1,2,3,4],"expiry":0,"terms":[33,33],"sig":[6,5,4,3],"range":{"id":1,"expiry":0,"terms":[1,2,3,3],"slots":[{"id":0,"app_id":42,"reserved":"","consumed":false,"expiry":0,"terms":[0]}]}}' -H: "Content-type: application/json" localhost:8080/api/v1/reservation
The byte values really don't matter for now, I am just interested to get the deserialization going right first. Is that the correct way of putting byte values in JSON?
I have also tried with just leaving out the nested struct fields:
curl -v -X PUT -H: "Content-type: application/json" -d '{"id":-1,"pub_key":[1,2,3,4],"expiry":0,"terms":[33,33],"sig":[6,5,4,3],"range":{}}' localhost:8080/api/v1/reservation
One of the other numerous attempts has involved "prefixing" the JSON object with the struct name:
-d {"reservation":{...}}
But that also didn't help.
I couldn't find much documentation in the serde docs specifically about byte vectors (did I just not look right?), but maybe I am not getting something way more basic yet. Or maybe I am oversimplifying things from looking at the (usual) simple example in the docs.
Can someone shed light here please? Thanks.