diff --git a/boilmaster.toml b/boilmaster.toml index cdbfbb5..31ae481 100644 --- a/boilmaster.toml +++ b/boilmaster.toml @@ -18,8 +18,8 @@ limit.depth = 2 # TODO: should this be shared with search eventually, or nah? filter.exdschema.list = "Name,Singular,Icon" -[data] -language = "en" +[read.language] +default = "en" [version] interval = 3600 # 1 hour diff --git a/src/data/data.rs b/src/data/data.rs index bdc18eb..9fba5dc 100644 --- a/src/data/data.rs +++ b/src/data/data.rs @@ -4,30 +4,15 @@ use std::{ }; use anyhow::Context; -use ironworks::{ - excel::{Excel, Language}, - sqpack::SqPack, - zipatch, Ironworks, -}; -use serde::Deserialize; +use ironworks::{excel::Excel, sqpack::SqPack, zipatch, Ironworks}; use tokio::{select, sync::watch}; use tokio_util::sync::CancellationToken; use crate::version::{self, VersionKey}; -use super::{ - error::{Error, Result}, - language::LanguageString, -}; - -#[derive(Debug, Deserialize)] -pub struct Config { - language: LanguageString, -} +use super::error::{Error, Result}; pub struct Data { - default_language: Language, - channel: watch::Sender>, // Root ZiPatch instance, acts as a LUT cache @@ -37,11 +22,10 @@ pub struct Data { } impl Data { - pub fn new(config: Config) -> Self { + pub fn new() -> Self { let (sender, _receiver) = watch::channel(vec![]); Data { - default_language: config.language.into(), channel: sender, zipatch: zipatch::ZiPatch::new().with_persisted_lookups(), versions: Default::default(), @@ -54,10 +38,6 @@ impl Data { self.versions.read().expect("poisoned").len() > 0 } - pub fn default_language(&self) -> Language { - self.default_language - } - pub fn subscribe(&self) -> watch::Receiver> { self.channel.subscribe() } diff --git a/src/data/error.rs b/src/data/error.rs index 1665db5..f8fbd3b 100644 --- a/src/data/error.rs +++ b/src/data/error.rs @@ -5,9 +5,6 @@ pub enum Error { #[error("unknown version {0}")] UnknownVersion(VersionKey), - #[error("unknown language \"{0}\"")] - UnknownLanguage(String), - #[error(transparent)] Failure(#[from] anyhow::Error), } diff --git a/src/data/mod.rs b/src/data/mod.rs index 0fd3dfa..9c9e632 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,9 +1,7 @@ mod data; mod error; -mod language; pub use { - data::{Config, Data, Version}, + data::{Data, Version}, error::Error, - language::LanguageString, }; diff --git a/src/http/api1/error.rs b/src/http/api1/error.rs index bca1d0b..ee07e35 100644 --- a/src/http/api1/error.rs +++ b/src/http/api1/error.rs @@ -48,7 +48,7 @@ impl From for Error { fn from(error: data::Error) -> Self { use data::Error as DE; match error { - DE::UnknownVersion(..) | DE::UnknownLanguage(..) => Self::Invalid(error.to_string()), + DE::UnknownVersion(..) => Self::Invalid(error.to_string()), DE::Failure(inner) => Self::Other(inner), } } @@ -59,7 +59,7 @@ impl From for Error { use read::Error as RE; match error { RE::NotFound(..) => Self::NotFound(error.to_string()), - RE::FilterSchemaMismatch(..) | RE::SchemaGameMismatch(..) => { + RE::FilterSchemaMismatch(..) | RE::SchemaGameMismatch(..) | RE::InvalidLanguage(..) => { Self::Invalid(error.to_string()) } RE::Failure(inner) => Self::Other(inner), diff --git a/src/http/api1/filter.rs b/src/http/api1/filter.rs index d27eb3d..0ef933f 100644 --- a/src/http/api1/filter.rs +++ b/src/http/api1/filter.rs @@ -14,7 +14,7 @@ use nom::{ use schemars::JsonSchema; use serde::{de, Deserialize}; -use crate::{data, read}; +use crate::read; use super::error; @@ -196,7 +196,7 @@ fn index(input: &str) -> IResult<&str, Entry> { fn language(input: &str) -> IResult<&str, excel::Language> { map_res(alphanumeric1, |string: &str| { string - .parse::() + .parse::() .map(excel::Language::from) })(input) } diff --git a/src/http/api1/sheet.rs b/src/http/api1/sheet.rs index 8c3513e..c1f0aa2 100644 --- a/src/http/api1/sheet.rs +++ b/src/http/api1/sheet.rs @@ -15,7 +15,6 @@ use schemars::{ use serde::{de, Deserialize, Deserializer, Serialize}; use crate::{ - data::LanguageString, http::service, read, schema, utility::{anyhow::Anyhow, jsonschema::impl_jsonschema}, @@ -145,7 +144,7 @@ fn rowspecifier_schema(_generator: &mut SchemaGenerator) -> Schema { struct SheetQuery { // Data resolution /// Language to use for data with no language otherwise specified in the fields filter. - language: Option, + language: Option, /// Schema that row data should be read with. schema: Option, @@ -247,6 +246,7 @@ async fn sheet( VersionQuery(version_key): VersionQuery, Query(query): Query, State(data): State, + State(read): State, State(schema_provider): State, Extension(config): Extension, ) -> Result { @@ -256,7 +256,7 @@ async fn sheet( let language = query .language .map(excel::Language::from) - .unwrap_or_else(|| data.default_language()); + .unwrap_or_else(|| read.default_language()); // TODO: Consider extractor for this. let schema_specifier = schema_provider.canonicalize(query.schema, version_key)?; @@ -318,7 +318,7 @@ async fn sheet( // TODO: This is pretty wasteful to call inside a loop, revisit actual read logic. // TODO: at the moment, an unknown row specifier will cause excel to error with a NotFound (which is fine), however read:: then squashes that with anyhow, meaning the error gets hidden in a 500 ISE. revisit error handling in read:: while i'm at it ref. the above. - let fields = read::read( + let fields = read.read( &excel, schema.as_ref(), &path.sheet, @@ -362,7 +362,7 @@ struct RowPath { #[derive(Deserialize, JsonSchema)] struct RowQuery { /// Language to use for data with no language otherwise specified in the fields filter. - language: Option, + language: Option, /// Schema that row data should be read with. schema: Option, @@ -422,6 +422,7 @@ async fn row( VersionQuery(version_key): VersionQuery, Query(query): Query, State(data): State, + State(read): State, State(schema_provider): State, Extension(config): Extension, ) -> Result { @@ -430,7 +431,7 @@ async fn row( let language = query .language .map(excel::Language::from) - .unwrap_or_else(|| data.default_language()); + .unwrap_or_else(|| read.default_language()); let schema_specifier = schema_provider.canonicalize(query.schema, version_key)?; @@ -450,7 +451,7 @@ async fn row( let row_id = path.row.row_id; let subrow_id = path.row.subrow_id; - let fields = read::read( + let fields = read.read( &excel, schema.as_ref(), &path.sheet, diff --git a/src/http/api1/value.rs b/src/http/api1/value.rs index 88dd306..2d4e539 100644 --- a/src/http/api1/value.rs +++ b/src/http/api1/value.rs @@ -7,7 +7,7 @@ use schemars::{ }; use serde::ser::{Serialize, SerializeMap, SerializeSeq, SerializeStruct}; -use crate::{data, read, utility::jsonschema::impl_jsonschema}; +use crate::{read, utility::jsonschema::impl_jsonschema}; #[derive(Debug)] pub struct ValueString(pub read::Value, pub excel::Language); @@ -155,7 +155,7 @@ impl ValueReference<'_> { .map(|(read::StructKey { name, language }, value)| { let key = match *language == self.language { true => name.to_owned(), - false => format!("{name}@{}", data::LanguageString::from(*language)), + false => format!("{name}@{}", read::LanguageString::from(*language)), }; (key, value) diff --git a/src/http/http.rs b/src/http/http.rs index 949d6fa..86ca404 100644 --- a/src/http/http.rs +++ b/src/http/http.rs @@ -27,8 +27,9 @@ pub struct Config { pub async fn serve( cancel: CancellationToken, config: Config, - data: service::Data, asset: service::Asset, + data: service::Data, + read: service::Read, schema: service::Schema, // search: service::Search, version: service::Version, @@ -49,6 +50,7 @@ pub async fn serve( .with_state(service::State { asset, data, + read, schema, // search, version, diff --git a/src/http/service.rs b/src/http/service.rs index ba8c600..737f46c 100644 --- a/src/http/service.rs +++ b/src/http/service.rs @@ -5,6 +5,7 @@ use axum::extract::FromRef; use crate::{ asset, data, + read, schema, // search, version, @@ -12,6 +13,7 @@ use crate::{ pub type Asset = Arc; pub type Data = Arc; +pub type Read = Arc; pub type Schema = Arc; // pub type Search = Arc; pub type Version = Arc; @@ -20,6 +22,7 @@ pub type Version = Arc; pub struct State { pub asset: Asset, pub data: Data, + pub read: Read, pub schema: Schema, // pub search: Search, pub version: Version, diff --git a/src/lib.rs b/src/lib.rs index 019059d..046c804 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ pub mod asset; pub mod data; pub mod http; -mod read; +pub mod read; pub mod schema; // pub mod search; pub mod tracing; diff --git a/src/main.rs b/src/main.rs index 5019181..2b65821 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use boilmaster::{ asset, data, http, + read, schema, // search, tracing, @@ -23,7 +24,7 @@ use tokio_util::sync::CancellationToken; struct Config { // tracing: tracing::Config, - read individually. http: http::Config, - data: data::Config, + read: read::Config, version: version::Config, schema: schema::Config, // search: search::Config, @@ -54,8 +55,9 @@ async fn main() -> anyhow::Result<()> { let version = Arc::new( version::Manager::new(config.version).context("failed to create version manager")?, ); - let data = Arc::new(data::Data::new(config.data)); + let data = Arc::new(data::Data::new()); let asset = Arc::new(asset::Service::new(data.clone())); + let read = Arc::new(read::Read::new(config.read)); let schema = Arc::new( schema::Provider::new(config.schema, data.clone()) .context("failed to create schema provider")?, @@ -78,8 +80,9 @@ async fn main() -> anyhow::Result<()> { http::serve( shutdown_token, config.http, - data.clone(), asset, + data.clone(), + read, schema.clone(), // search.clone(), version.clone(), diff --git a/src/read/error.rs b/src/read/error.rs index 3ee951f..5ba8f88 100644 --- a/src/read/error.rs +++ b/src/read/error.rs @@ -4,6 +4,9 @@ pub enum Error { #[error("{0}")] NotFound(String), + #[error("invalid or unsupported language \"{0}\"")] + InvalidLanguage(String), + /// The provided filter does not map cleanly onto the sheet schema. #[error("filter <-> schema mismatch on {}: {}", .0.field, .0.reason)] FilterSchemaMismatch(MismatchError), diff --git a/src/read/filter.rs b/src/read/filter.rs index 4858d35..35b5bf4 100644 --- a/src/read/filter.rs +++ b/src/read/filter.rs @@ -10,6 +10,7 @@ pub enum Filter { All, } +// TODO: Merge with LanguageString? #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Language(pub excel::Language); impl IsEnabled for Language {} diff --git a/src/data/language.rs b/src/read/language.rs similarity index 97% rename from src/data/language.rs rename to src/read/language.rs index 2005c7c..cd2b633 100644 --- a/src/data/language.rs +++ b/src/read/language.rs @@ -61,7 +61,7 @@ impl FromStr for LanguageString { "chs" => Language::ChineseSimplified, "cht" => Language::ChineseTraditional, "kr" => Language::Korean, - _ => return Err(Error::UnknownLanguage(string.into())), + _ => return Err(Error::InvalidLanguage(string.into())), }; Ok(Self(language)) diff --git a/src/read/mod.rs b/src/read/mod.rs index 600f315..56d8990 100644 --- a/src/read/mod.rs +++ b/src/read/mod.rs @@ -1,11 +1,13 @@ mod error; mod filter; +mod language; mod read; mod value; pub use { error::Error, filter::{Filter, Language}, - read::read, + language::LanguageString, + read::{Config, Read}, value::{Reference, StructKey, Value}, }; diff --git a/src/read/read.rs b/src/read/read.rs index 08d43f3..fa7356f 100644 --- a/src/read/read.rs +++ b/src/read/read.rs @@ -9,44 +9,73 @@ use anyhow::{anyhow, Context}; use ironworks::{excel, file::exh}; use ironworks_schema as schema; use nohash_hasher::IntMap; +use serde::Deserialize; use crate::read::Language; use super::{ error::{Error, MismatchError, Result}, filter::Filter, + language::LanguageString, value::{Reference, StructKey, Value}, }; -pub fn read( - excel: &excel::Excel, - schema: &dyn schema::Schema, +#[derive(Debug, Deserialize)] +pub struct Config { + language: LanguageConfig, +} - sheet_name: &str, - row_id: u32, - subrow_id: u16, +#[derive(Debug, Deserialize)] +struct LanguageConfig { + default: LanguageString, +} +pub struct Read { default_language: excel::Language, +} - filter: &Filter, - depth: u8, -) -> Result { - let value = read_sheet(ReaderContext { - excel, - schema, +impl Read { + pub fn new(config: Config) -> Self { + Self { + default_language: config.language.default.into(), + } + } + + pub fn default_language(&self) -> excel::Language { + self.default_language + } - sheet: sheet_name, - language: default_language, - row_id, - subrow_id, + pub fn read( + &self, + excel: &excel::Excel, + schema: &dyn schema::Schema, - filter, - rows: &mut HashMap::new(), - columns: &[], - depth, - })?; + sheet_name: &str, + row_id: u32, + subrow_id: u16, - Ok(value) + default_language: excel::Language, + + filter: &Filter, + depth: u8, + ) -> Result { + let value = read_sheet(ReaderContext { + excel, + schema, + + sheet: sheet_name, + language: default_language, + row_id, + subrow_id, + + filter, + rows: &mut HashMap::new(), + columns: &[], + depth, + })?; + + Ok(value) + } } fn read_sheet(context: ReaderContext) -> Result {