Rust API - Criando a rota de get all com rust - Part IV


Bem, vamos continuar o nosso trabalho, creio que daqui pra frente vai ser mais tranquilo, nesse momento queremos adicionar a nossa rota de get_all_tasks, ela vai ser responsável por buscar todas as tasks que criamos com a nossa rota de create_task.

Para começar vamos adicionar no nosso schema.rs o nosso filter options, que vai ser responsável por no final podermos paginar a nossa aplicação:

  #[derive(Deserialize, Debug)]
  pub struct FilterOptions {
      pub page: Option<usize>,
      pub limit: Option<usize>,
  }

Depois iremos para o nosso arquivo de service, services, onde iremos criar uma outra rota:

  pub async fn get_all_tasks(
    opts: Query<FilterOptions>,
    data: Data<AppState>
  ) -> impl Responder {}

Após isso vamos começar a trabalhar a nossa service, fazendo o import do nosso FilterOptions e chamar no nosso actix_web o nosso Query que vai ser usado para a parte do nosso select com options:

use crate::{
    model::TaskModel,
    schema:: {
        CreateTaskSchema,
        FilterOptions
    }, AppState
};
use actix_web::{
    get,
    post,
    web::{
        Data,
        Json,
        scope,
        Query,
        ServiceConfig
    },
    HttpResponse,
    Responder
};

a difinir o base do nosso limit e offset da nossa aplicação:

  pub async fn get_all_tasks(
    opts: Query<FilterOptions>,
    data: Data<AppState>
  ) -> impl Responder {
    let limit = opts.limit.unwrap_or(10);
    let offset = (opts.page.unwrap_or(1) - 1) * limit;

  }

O próximo passo é criar o nosso script de busca passando o nosso limit, offset e fazer o nosso SELECT com um tratamento da mensagem e do nosso erro:

 match
    sqlx
      ::query_as!(
          TaskModel,
          "SELECT * FROM tasks ORDER by id LIMIT $1 OFFSET $2",
          limit as i32,
          offset as i32
      )
      .fetch_all(&data.db)
      .await {
          Ok(tasks) => {
            let json_response = json!({
                "status": "success",
                "result":  tasks.len(),
                "tasks": tasks
            });

            return HttpResponse::Ok().json(json_response);
          }
          Err(error) => {
              return HttpResponse::InternalServerError().json(
                  json!({
                      "status": "error",
                      "message": format!("{:?}", error)
                  })
              )
          }
    }

A função toda fica assim:

#[get("/tasks")]
pub async fn get_all_tasks(opts: Query<FilterOptions>, data: Data<AppState>) -> impl Responder {
    let limit = opts.limit.unwrap_or(10);
    let offset = (opts.page.unwrap_or(1) - 1) * limit;

    match
        sqlx
            ::query_as!(
                TaskModel,
                "SELECT * FROM tasks ORDER by id LIMIT $1 OFFSET $2",
                limit as i32,
                offset as i32
            )
            .fetch_all(&data.db)
            .await {
                Ok(tasks) => {
                  let json_response = json!({
                      "status": "success",
                      "result":  tasks.len(),
                      "tasks": tasks
                  });

                  return HttpResponse::Ok().json(json_response);
                }
                Err(error) => {
                    return HttpResponse::InternalServerError().json(
                        json!({
                            "status": "error",
                            "message": format!("{:?}", error)
                        })
                    )
                }
            }
}

No final do nosso arquivo, só precisamos adicionar a nossa rota a função pública que temos com as nossas configurações:

pub fn config(conf: &mut ServiceConfig) {
    let scope = scope("/api")
        .service(health_checker)
        .service(create_task)
        .service(get_all_tasks);

    conf.service(scope);
}

No final o nosso schema.rs fica assim:

  src/schema.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateTaskSchema {
    pub title: String,
    pub content: String,
}

#[derive(Deserialize, Debug)]
pub struct FilterOptions {
    pub page: Option<usize>,
    pub limit: Option<usize>,
}

E a nossa services.rs como um todo ficará assim:

  src/services.rs
use crate::{
    model::TaskModel,
    schema:: {
        CreateTaskSchema,
        FilterOptions
    }, AppState
};
use actix_web::{
    get,
    post,
    web::{
        Data,
        Json,
        scope,
        Query,
        ServiceConfig
    },
    HttpResponse,
    Responder
};
use serde_json::json;

#[get("/healthchecker")]
async fn health_checker() -> impl Responder {
    const MESSAGE: &str = "Health check API is up and running smoothly.";
    HttpResponse::Ok().json(json!({"status": "success", "message": MESSAGE }))
}

#[post("/task")]
async fn create_task(body: Json<CreateTaskSchema>, data: Data<AppState>) -> impl Responder {
    match
        sqlx
            ::query_as!(
                TaskModel,
                "INSERT INTO tasks (title, content) VALUES ($1, $2)
                RETURNING * ",
                body.title.to_string(),
                body.content.to_string()
            )
            .fetch_one(&data.db)
            .await {
            Ok(tasks) => {
              let json_response = json!({
                  "status": "success",
                  "result":  tasks.len(),
                  "tasks": tasks
              });

              return HttpResponse::Ok().json(json_response);
            }
            Err(error) => {
                return HttpResponse::InternalServerError().json(
                    json!({
                        "status": "error",
                        "message": format!("{:?}", error)
                    })
                )
            }
        }
}

#[get("/tasks")]
pub async fn get_all_tasks(opts: Query<FilterOptions>, data: Data<AppState>) -> impl Responder {
    let limit = opts.limit.unwrap_or(10);
    let offset = (opts.page.unwrap_or(1) - 1) * limit;

    match
        sqlx
            ::query_as!(
                TaskModel,
                "SELECT * FROM tasks ORDER by id LIMIT $1 OFFSET $2",
                limit as i32,
                offset as i32
            )
            .fetch_all(&data.db)
            .await {
                Ok(tasks) => {
                  let json_response = json!({
                      "status": "success",
                      "result":  tasks.len(),
                      "tasks": tasks
                  });

                  return HttpResponse::Ok().json(json_response);
                }
                Err(error) => {
                    return HttpResponse::InternalServerError().json(
                        json!({
                            "status": "error",
                            "message": format!("{:?}", error)
                        })
                    )
                }
            }
}

pub fn config(conf: &mut ServiceConfig) {
    let scope = scope("/api")
        .service(health_checker)
        .service(create_task)
        .service(get_all_tasks);

    conf.service(scope);
}

Testando, você pode usar o insomnia apontando para a seguinte rota: http://localhost:8080/api/tasks

Ou com curl, testando direto do seu terminal:

curl --request GET \
  --url http://localhost:8080/api/tasks

E se tudo deu certo você vai ter a seguinte mensagem:

{
  "result": 2,
  "status": "success",
  "tasks": [
    {
      "content": "content test",
      "created_at": "2023-06-08T22:50:44.186036Z",
      "id": "dc512907-fbdb-4f97-b130-c0859dc2f2ef",
      "title": "title test2"
    },
    {
      "content": "content test",
      "created_at": "2023-06-08T21:41:25.586259Z",
      "id": "fc7e46df-8dd4-40a5-87f0-65d097d31e5a",
      "title": "title test"
    }
  ]
}

Próximo passo devemos criar uma nova rota para buscar uma task em específico e deletar a mesma, até mais!