etwin_populate 0.10.0

Hammerfest store implementation
Documentation
use etwin_constants::hammerfest::{ITEMS, ITEMS_BY_ID, QUESTS};
use etwin_core::hammerfest::{HammerfestItemId, HammerfestQuestId, HammerfestServer};
use sqlx::postgres::PgQueryResult;
use sqlx::{Postgres, Transaction};
use std::collections::BTreeSet;
use std::error::Error;

fn box_sqlx_error(e: sqlx::Error) -> Box<dyn Error + Send> {
  Box::new(e)
}

pub async fn populate_hammerfest(tx: &mut Transaction<'_, Postgres>) -> Result<(), Box<dyn Error + Send>> {
  populate_hammerfest_servers(tx).await?;
  populate_hammerfest_quests(tx).await?;
  populate_hammerfest_items(tx).await?;
  Ok(())
}

async fn populate_hammerfest_servers(tx: &mut Transaction<'_, Postgres>) -> Result<(), Box<dyn Error + Send>> {
  #[derive(Debug, sqlx::FromRow)]
  struct Row {
    hammerfest_server: HammerfestServer,
  }

  let rows: Vec<Row> = sqlx::query_as::<_, Row>(
    r"
      SELECT hammerfest_server
      FROM hammerfest_servers;
    ",
  )
  .fetch_all(&mut *tx)
  .await
  .map_err(box_sqlx_error)?;

  let actual: BTreeSet<_> = rows.iter().map(|r| r.hammerfest_server).collect();
  let expected: BTreeSet<_> = HammerfestServer::iter().collect();

  if actual == expected {
    return Ok(());
  }

  for extra in actual.difference(&expected) {
    let res: PgQueryResult = sqlx::query(
      r"
      DELETE
      FROM hammerfest_servers
      WHERE hammerfest_server = $1::hammerfest_server;
    ",
    )
    .bind(extra)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert_eq!(res.rows_affected(), 1);
  }

  for value in expected {
    let res: PgQueryResult = sqlx::query(
      r"
      INSERT
      INTO hammerfest_servers(hammerfest_server)
      VALUES ($1::hammerfest_server)
      ON CONFLICT (hammerfest_server) DO NOTHING;
    ",
    )
    .bind(value)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert!((0..=1u64).contains(&res.rows_affected()));
  }

  Ok(())
}

async fn populate_hammerfest_items(tx: &mut Transaction<'_, Postgres>) -> Result<(), Box<dyn Error + Send>> {
  #[derive(Debug, sqlx::FromRow)]
  struct Row {
    hammerfest_item_id: HammerfestItemId,
  }

  let rows: Vec<Row> = sqlx::query_as::<_, Row>(
    r"
      SELECT hammerfest_item_id
      FROM hammerfest_items;
    ",
  )
  .fetch_all(&mut *tx)
  .await
  .map_err(box_sqlx_error)?;

  let actual: BTreeSet<HammerfestItemId> = rows.iter().map(|r| r.hammerfest_item_id).collect();
  let expected: BTreeSet<HammerfestItemId> = ITEMS.iter().map(|q| q.id).collect();

  for extra_qid in actual.difference(&expected) {
    let res: PgQueryResult = sqlx::query(
      r"
      DELETE
      FROM hammerfest_items
      WHERE hammerfest_item_id = $1::hammerfest_item_id;
    ",
    )
    .bind(extra_qid)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert_eq!(res.rows_affected(), 1);
  }

  let items_by_id = &*ITEMS_BY_ID;
  for iid in expected {
    let item = *items_by_id.get(&iid).unwrap();
    let res: PgQueryResult = sqlx::query(
      r"
      INSERT
      INTO hammerfest_items(hammerfest_item_id, is_hidden)
      VALUES ($1::hammerfest_item_id, $2::BOOLEAN)
      ON CONFLICT (hammerfest_item_id) DO UPDATE SET is_hidden = $2::BOOLEAN;
    ",
    )
    .bind(item.id)
    .bind(item.is_hidden)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert_eq!(res.rows_affected(), 1);
  }

  Ok(())
}

async fn populate_hammerfest_quests(tx: &mut Transaction<'_, Postgres>) -> Result<(), Box<dyn Error + Send>> {
  #[derive(Debug, sqlx::FromRow)]
  struct Row {
    hammerfest_quest_id: HammerfestQuestId,
  }

  let rows: Vec<Row> = sqlx::query_as::<_, Row>(
    r"
      SELECT hammerfest_quest_id
      FROM hammerfest_quests;
    ",
  )
  .fetch_all(&mut *tx)
  .await
  .map_err(box_sqlx_error)?;

  let actual: BTreeSet<HammerfestQuestId> = rows.iter().map(|r| r.hammerfest_quest_id).collect();
  let expected: BTreeSet<HammerfestQuestId> = QUESTS.iter().map(|q| q.id).collect();

  if actual == expected {
    return Ok(());
  }

  for extra_qid in actual.difference(&expected) {
    let res: PgQueryResult = sqlx::query(
      r"
      DELETE
      FROM hammerfest_quests
      WHERE hammerfest_quest_id = $1::hammerfest_quest_id;
    ",
    )
    .bind(extra_qid)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert_eq!(res.rows_affected(), 1);
  }

  for qid in expected {
    let res: PgQueryResult = sqlx::query(
      r"
      INSERT
      INTO hammerfest_quests(hammerfest_quest_id)
      VALUES ($1::hammerfest_quest_id)
      ON CONFLICT (hammerfest_quest_id) DO NOTHING;
    ",
    )
    .bind(qid)
    .execute(&mut *tx)
    .await
    .map_err(box_sqlx_error)?;
    assert!((0..=1u64).contains(&res.rows_affected()));
  }

  Ok(())
}