mirror of
https://github.com/maplibre/martin.git
synced 2024-12-19 21:01:45 +03:00
Simplify conf parsing, separate func & tbl configs (#956)
* Simplify code by adding `None` to the enums we use for configuration * Separate postgres auto-publish configuration into table and function structs
This commit is contained in:
parent
196df9e806
commit
e377bd62ac
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -2842,9 +2842,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
@ -4,7 +4,7 @@ use crate::args::connections::Arguments;
|
|||||||
use crate::args::connections::State::{Ignore, Take};
|
use crate::args::connections::State::{Ignore, Take};
|
||||||
use crate::args::environment::Env;
|
use crate::args::environment::Env;
|
||||||
use crate::pg::{PgConfig, PgSslCerts, POOL_SIZE_DEFAULT};
|
use crate::pg::{PgConfig, PgSslCerts, POOL_SIZE_DEFAULT};
|
||||||
use crate::utils::OneOrMany;
|
use crate::utils::{OptBoolObj, OptOneMany};
|
||||||
|
|
||||||
#[derive(clap::Args, Debug, PartialEq, Default)]
|
#[derive(clap::Args, Debug, PartialEq, Default)]
|
||||||
#[command(about, version)]
|
#[command(about, version)]
|
||||||
@ -30,7 +30,7 @@ impl PgArgs {
|
|||||||
self,
|
self,
|
||||||
cli_strings: &mut Arguments,
|
cli_strings: &mut Arguments,
|
||||||
env: &impl Env<'a>,
|
env: &impl Env<'a>,
|
||||||
) -> Option<OneOrMany<PgConfig>> {
|
) -> OptOneMany<PgConfig> {
|
||||||
let connections = Self::extract_conn_strings(cli_strings, env);
|
let connections = Self::extract_conn_strings(cli_strings, env);
|
||||||
let default_srid = self.get_default_srid(env);
|
let default_srid = self.get_default_srid(env);
|
||||||
let certs = self.get_certs(env);
|
let certs = self.get_certs(env);
|
||||||
@ -48,20 +48,20 @@ impl PgArgs {
|
|||||||
},
|
},
|
||||||
max_feature_count: self.max_feature_count,
|
max_feature_count: self.max_feature_count,
|
||||||
pool_size: self.pool_size,
|
pool_size: self.pool_size,
|
||||||
auto_publish: None,
|
auto_publish: OptBoolObj::NoValue,
|
||||||
tables: None,
|
tables: None,
|
||||||
functions: None,
|
functions: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match results.len() {
|
match results.len() {
|
||||||
0 => None,
|
0 => OptOneMany::NoVals,
|
||||||
1 => Some(OneOrMany::One(results.into_iter().next().unwrap())),
|
1 => OptOneMany::One(results.into_iter().next().unwrap()),
|
||||||
_ => Some(OneOrMany::Many(results)),
|
_ => OptOneMany::Many(results),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn override_config<'a>(self, pg_config: &mut OneOrMany<PgConfig>, env: &impl Env<'a>) {
|
pub fn override_config<'a>(self, pg_config: &mut OptOneMany<PgConfig>, env: &impl Env<'a>) {
|
||||||
if self.default_srid.is_some() {
|
if self.default_srid.is_some() {
|
||||||
info!("Overriding configured default SRID to {} on all Postgres connections because of a CLI parameter", self.default_srid.unwrap());
|
info!("Overriding configured default SRID to {} on all Postgres connections because of a CLI parameter", self.default_srid.unwrap());
|
||||||
pg_config.iter_mut().for_each(|c| {
|
pg_config.iter_mut().for_each(|c| {
|
||||||
@ -224,10 +224,10 @@ mod tests {
|
|||||||
let config = PgArgs::default().into_config(&mut args, &FauxEnv::default());
|
let config = PgArgs::default().into_config(&mut args, &FauxEnv::default());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config,
|
config,
|
||||||
Some(OneOrMany::One(PgConfig {
|
OptOneMany::One(PgConfig {
|
||||||
connection_string: some("postgres://localhost:5432"),
|
connection_string: some("postgres://localhost:5432"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
})
|
||||||
);
|
);
|
||||||
assert!(args.check().is_ok());
|
assert!(args.check().is_ok());
|
||||||
}
|
}
|
||||||
@ -248,7 +248,7 @@ mod tests {
|
|||||||
let config = PgArgs::default().into_config(&mut args, &env);
|
let config = PgArgs::default().into_config(&mut args, &env);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config,
|
config,
|
||||||
Some(OneOrMany::One(PgConfig {
|
OptOneMany::One(PgConfig {
|
||||||
connection_string: some("postgres://localhost:5432"),
|
connection_string: some("postgres://localhost:5432"),
|
||||||
default_srid: Some(10),
|
default_srid: Some(10),
|
||||||
ssl_certificates: PgSslCerts {
|
ssl_certificates: PgSslCerts {
|
||||||
@ -256,7 +256,7 @@ mod tests {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
})
|
||||||
);
|
);
|
||||||
assert!(args.check().is_ok());
|
assert!(args.check().is_ok());
|
||||||
}
|
}
|
||||||
@ -282,7 +282,7 @@ mod tests {
|
|||||||
let config = pg_args.into_config(&mut args, &env);
|
let config = pg_args.into_config(&mut args, &env);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config,
|
config,
|
||||||
Some(OneOrMany::One(PgConfig {
|
OptOneMany::One(PgConfig {
|
||||||
connection_string: some("postgres://localhost:5432"),
|
connection_string: some("postgres://localhost:5432"),
|
||||||
default_srid: Some(20),
|
default_srid: Some(20),
|
||||||
ssl_certificates: PgSslCerts {
|
ssl_certificates: PgSslCerts {
|
||||||
@ -291,7 +291,7 @@ mod tests {
|
|||||||
ssl_root_cert: Some(PathBuf::from("root")),
|
ssl_root_cert: Some(PathBuf::from("root")),
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
})
|
||||||
);
|
);
|
||||||
assert!(args.check().is_ok());
|
assert!(args.check().is_ok());
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,11 @@ impl Args {
|
|||||||
|
|
||||||
let mut cli_strings = Arguments::new(self.meta.connection);
|
let mut cli_strings = Arguments::new(self.meta.connection);
|
||||||
let pg_args = self.pg.unwrap_or_default();
|
let pg_args = self.pg.unwrap_or_default();
|
||||||
if let Some(pg_config) = &mut config.postgres {
|
if config.postgres.is_none() {
|
||||||
// config was loaded from a file, we can only apply a few CLI overrides to it
|
|
||||||
pg_args.override_config(pg_config, env);
|
|
||||||
} else {
|
|
||||||
config.postgres = pg_args.into_config(&mut cli_strings, env);
|
config.postgres = pg_args.into_config(&mut cli_strings, env);
|
||||||
|
} else {
|
||||||
|
// config was loaded from a file, we can only apply a few CLI overrides to it
|
||||||
|
pg_args.override_config(&mut config.postgres, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cli_strings.is_empty() {
|
if !cli_strings.is_empty() {
|
||||||
@ -85,7 +85,7 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_file_args(cli_strings: &mut Arguments, extension: &str) -> Option<FileConfigEnum> {
|
pub fn parse_file_args(cli_strings: &mut Arguments, extension: &str) -> FileConfigEnum {
|
||||||
let paths = cli_strings.process(|v| match PathBuf::try_from(v) {
|
let paths = cli_strings.process(|v| match PathBuf::try_from(v) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
if v.is_dir() {
|
if v.is_dir() {
|
||||||
@ -107,7 +107,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::pg::PgConfig;
|
use crate::pg::PgConfig;
|
||||||
use crate::test_utils::{some, FauxEnv};
|
use crate::test_utils::{some, FauxEnv};
|
||||||
use crate::utils::OneOrMany;
|
use crate::utils::OptOneMany;
|
||||||
|
|
||||||
fn parse(args: &[&str]) -> Result<(Config, MetaArgs)> {
|
fn parse(args: &[&str]) -> Result<(Config, MetaArgs)> {
|
||||||
let args = Args::parse_from(args);
|
let args = Args::parse_from(args);
|
||||||
@ -143,10 +143,10 @@ mod tests {
|
|||||||
|
|
||||||
let args = parse(&["martin", "postgres://connection"]).unwrap();
|
let args = parse(&["martin", "postgres://connection"]).unwrap();
|
||||||
let cfg = Config {
|
let cfg = Config {
|
||||||
postgres: Some(OneOrMany::One(PgConfig {
|
postgres: OptOneMany::One(PgConfig {
|
||||||
connection_string: some("postgres://connection"),
|
connection_string: some("postgres://connection"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let meta = MetaArgs {
|
let meta = MetaArgs {
|
||||||
|
@ -17,7 +17,7 @@ use crate::source::{TileInfoSources, TileSources};
|
|||||||
use crate::sprites::SpriteSources;
|
use crate::sprites::SpriteSources;
|
||||||
use crate::srv::SrvConfig;
|
use crate::srv::SrvConfig;
|
||||||
use crate::Error::{ConfigLoadError, ConfigParseError, NoSources};
|
use crate::Error::{ConfigLoadError, ConfigParseError, NoSources};
|
||||||
use crate::{IdResolver, OneOrMany, Result};
|
use crate::{IdResolver, OptOneMany, Result};
|
||||||
|
|
||||||
pub type UnrecognizedValues = HashMap<String, serde_yaml::Value>;
|
pub type UnrecognizedValues = HashMap<String, serde_yaml::Value>;
|
||||||
|
|
||||||
@ -31,17 +31,17 @@ pub struct Config {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub srv: SrvConfig,
|
pub srv: SrvConfig,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
pub postgres: Option<OneOrMany<PgConfig>>,
|
pub postgres: OptOneMany<PgConfig>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "FileConfigEnum::is_none")]
|
||||||
pub pmtiles: Option<FileConfigEnum>,
|
pub pmtiles: FileConfigEnum,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "FileConfigEnum::is_none")]
|
||||||
pub mbtiles: Option<FileConfigEnum>,
|
pub mbtiles: FileConfigEnum,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "FileConfigEnum::is_none")]
|
||||||
pub sprites: Option<FileConfigEnum>,
|
pub sprites: FileConfigEnum,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub unrecognized: UnrecognizedValues,
|
pub unrecognized: UnrecognizedValues,
|
||||||
@ -53,40 +53,22 @@ impl Config {
|
|||||||
let mut res = UnrecognizedValues::new();
|
let mut res = UnrecognizedValues::new();
|
||||||
copy_unrecognized_config(&mut res, "", &self.unrecognized);
|
copy_unrecognized_config(&mut res, "", &self.unrecognized);
|
||||||
|
|
||||||
let mut any = if let Some(pg) = &mut self.postgres {
|
for pg in self.postgres.iter_mut() {
|
||||||
for pg in pg.iter_mut() {
|
res.extend(pg.finalize()?);
|
||||||
res.extend(pg.finalize()?);
|
}
|
||||||
}
|
|
||||||
!pg.is_empty()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
any |= if let Some(cfg) = &mut self.pmtiles {
|
res.extend(self.pmtiles.finalize("pmtiles.")?);
|
||||||
res.extend(cfg.finalize("pmtiles.")?);
|
res.extend(self.mbtiles.finalize("mbtiles.")?);
|
||||||
!cfg.is_empty()
|
res.extend(self.sprites.finalize("sprites.")?);
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
any |= if let Some(cfg) = &mut self.mbtiles {
|
if self.postgres.is_empty()
|
||||||
res.extend(cfg.finalize("mbtiles.")?);
|
&& self.pmtiles.is_empty()
|
||||||
!cfg.is_empty()
|
&& self.mbtiles.is_empty()
|
||||||
} else {
|
&& self.sprites.is_empty()
|
||||||
false
|
{
|
||||||
};
|
|
||||||
|
|
||||||
any |= if let Some(cfg) = &mut self.sprites {
|
|
||||||
res.extend(cfg.finalize("sprites.")?);
|
|
||||||
!cfg.is_empty()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if any {
|
|
||||||
Ok(res)
|
|
||||||
} else {
|
|
||||||
Err(NoSources)
|
Err(NoSources)
|
||||||
|
} else {
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,18 +84,16 @@ impl Config {
|
|||||||
let create_mbt_src = &mut MbtSource::new_box;
|
let create_mbt_src = &mut MbtSource::new_box;
|
||||||
let mut sources: Vec<Pin<Box<dyn Future<Output = Result<TileInfoSources>>>>> = Vec::new();
|
let mut sources: Vec<Pin<Box<dyn Future<Output = Result<TileInfoSources>>>>> = Vec::new();
|
||||||
|
|
||||||
if let Some(v) = self.postgres.as_mut() {
|
for s in self.postgres.iter_mut() {
|
||||||
for s in v.iter_mut() {
|
sources.push(Box::pin(s.resolve(idr.clone())));
|
||||||
sources.push(Box::pin(s.resolve(idr.clone())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pmtiles.is_some() {
|
if !self.pmtiles.is_empty() {
|
||||||
let val = resolve_files(&mut self.pmtiles, idr.clone(), "pmtiles", create_pmt_src);
|
let val = resolve_files(&mut self.pmtiles, idr.clone(), "pmtiles", create_pmt_src);
|
||||||
sources.push(Box::pin(val));
|
sources.push(Box::pin(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.mbtiles.is_some() {
|
if !self.mbtiles.is_empty() {
|
||||||
let val = resolve_files(&mut self.mbtiles, idr.clone(), "mbtiles", create_mbt_src);
|
let val = resolve_files(&mut self.mbtiles, idr.clone(), "mbtiles", create_mbt_src);
|
||||||
sources.push(Box::pin(val));
|
sources.push(Box::pin(val));
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::config::{copy_unrecognized_config, UnrecognizedValues};
|
use crate::config::{copy_unrecognized_config, UnrecognizedValues};
|
||||||
use crate::file_config::FileError::{InvalidFilePath, InvalidSourceFilePath, IoError};
|
use crate::file_config::FileError::{InvalidFilePath, InvalidSourceFilePath, IoError};
|
||||||
use crate::source::{Source, TileInfoSources};
|
use crate::source::{Source, TileInfoSources};
|
||||||
use crate::utils::{sorted_opt_map, Error, IdResolver, OneOrMany};
|
use crate::utils::{sorted_opt_map, Error, IdResolver, OptOneMany};
|
||||||
use crate::OneOrMany::{Many, One};
|
use crate::OptOneMany::{Many, One};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum FileError {
|
pub enum FileError {
|
||||||
@ -31,9 +31,11 @@ pub enum FileError {
|
|||||||
AquireConnError(String),
|
AquireConnError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum FileConfigEnum {
|
pub enum FileConfigEnum {
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
Paths(Vec<PathBuf>),
|
Paths(Vec<PathBuf>),
|
||||||
Config(FileConfig),
|
Config(FileConfig),
|
||||||
@ -41,7 +43,7 @@ pub enum FileConfigEnum {
|
|||||||
|
|
||||||
impl FileConfigEnum {
|
impl FileConfigEnum {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(paths: Vec<PathBuf>) -> Option<FileConfigEnum> {
|
pub fn new(paths: Vec<PathBuf>) -> FileConfigEnum {
|
||||||
Self::new_extended(paths, HashMap::new(), UnrecognizedValues::new())
|
Self::new_extended(paths, HashMap::new(), UnrecognizedValues::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,46 +52,70 @@ impl FileConfigEnum {
|
|||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
configs: HashMap<String, FileConfigSrc>,
|
configs: HashMap<String, FileConfigSrc>,
|
||||||
unrecognized: UnrecognizedValues,
|
unrecognized: UnrecognizedValues,
|
||||||
) -> Option<FileConfigEnum> {
|
) -> FileConfigEnum {
|
||||||
if configs.is_empty() && unrecognized.is_empty() {
|
if configs.is_empty() && unrecognized.is_empty() {
|
||||||
match paths.len() {
|
match paths.len() {
|
||||||
0 => None,
|
0 => FileConfigEnum::None,
|
||||||
1 => Some(FileConfigEnum::Path(paths.into_iter().next().unwrap())),
|
1 => FileConfigEnum::Path(paths.into_iter().next().unwrap()),
|
||||||
_ => Some(FileConfigEnum::Paths(paths)),
|
_ => FileConfigEnum::Paths(paths),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(FileConfigEnum::Config(FileConfig {
|
FileConfigEnum::Config(FileConfig {
|
||||||
paths: OneOrMany::new_opt(paths),
|
paths: OptOneMany::new(paths),
|
||||||
sources: if configs.is_empty() {
|
sources: if configs.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(configs)
|
Some(configs)
|
||||||
},
|
},
|
||||||
unrecognized,
|
unrecognized,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_file_config(&mut self) -> FileConfig {
|
#[must_use]
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
FileConfigEnum::Path(path) => FileConfig {
|
Self::None => true,
|
||||||
paths: Some(One(mem::take(path))),
|
Self::Path(_) => false,
|
||||||
..FileConfig::default()
|
Self::Paths(v) => v.is_empty(),
|
||||||
},
|
Self::Config(c) => c.is_empty(),
|
||||||
FileConfigEnum::Paths(paths) => FileConfig {
|
|
||||||
paths: Some(Many(mem::take(paths))),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
FileConfigEnum::Config(cfg) => mem::take(cfg),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extract_file_config(&mut self) -> Option<FileConfig> {
|
||||||
|
match self {
|
||||||
|
FileConfigEnum::None => None,
|
||||||
|
FileConfigEnum::Path(path) => Some(FileConfig {
|
||||||
|
paths: One(mem::take(path)),
|
||||||
|
..FileConfig::default()
|
||||||
|
}),
|
||||||
|
FileConfigEnum::Paths(paths) => Some(FileConfig {
|
||||||
|
paths: Many(mem::take(paths)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
FileConfigEnum::Config(cfg) => Some(mem::take(cfg)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize(&self, prefix: &str) -> Result<UnrecognizedValues, Error> {
|
||||||
|
let mut res = UnrecognizedValues::new();
|
||||||
|
if let Self::Config(cfg) = self {
|
||||||
|
copy_unrecognized_config(&mut res, prefix, &cfg.unrecognized);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct FileConfig {
|
pub struct FileConfig {
|
||||||
/// A list of file paths
|
/// A list of file paths
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
pub paths: Option<OneOrMany<PathBuf>>,
|
pub paths: OptOneMany<PathBuf>,
|
||||||
/// A map of source IDs to file paths or config objects
|
/// A map of source IDs to file paths or config objects
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(serialize_with = "sorted_opt_map")]
|
#[serde(serialize_with = "sorted_opt_map")]
|
||||||
@ -128,27 +154,8 @@ pub struct FileConfigSource {
|
|||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileConfigEnum {
|
|
||||||
pub fn finalize(&self, prefix: &str) -> Result<UnrecognizedValues, Error> {
|
|
||||||
let mut res = UnrecognizedValues::new();
|
|
||||||
if let Self::Config(cfg) = self {
|
|
||||||
copy_unrecognized_config(&mut res, prefix, &cfg.unrecognized);
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Path(_) => false,
|
|
||||||
Self::Paths(v) => v.is_empty(),
|
|
||||||
Self::Config(c) => c.is_empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn resolve_files<Fut>(
|
pub async fn resolve_files<Fut>(
|
||||||
config: &mut Option<FileConfigEnum>,
|
config: &mut FileConfigEnum,
|
||||||
idr: IdResolver,
|
idr: IdResolver,
|
||||||
extension: &str,
|
extension: &str,
|
||||||
create_source: &mut impl FnMut(String, PathBuf) -> Fut,
|
create_source: &mut impl FnMut(String, PathBuf) -> Fut,
|
||||||
@ -162,7 +169,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_int<Fut>(
|
async fn resolve_int<Fut>(
|
||||||
config: &mut Option<FileConfigEnum>,
|
config: &mut FileConfigEnum,
|
||||||
idr: IdResolver,
|
idr: IdResolver,
|
||||||
extension: &str,
|
extension: &str,
|
||||||
create_source: &mut impl FnMut(String, PathBuf) -> Fut,
|
create_source: &mut impl FnMut(String, PathBuf) -> Fut,
|
||||||
@ -170,10 +177,9 @@ async fn resolve_int<Fut>(
|
|||||||
where
|
where
|
||||||
Fut: Future<Output = Result<Box<dyn Source>, FileError>>,
|
Fut: Future<Output = Result<Box<dyn Source>, FileError>>,
|
||||||
{
|
{
|
||||||
let Some(cfg) = config else {
|
let Some(cfg) = config.extract_file_config() else {
|
||||||
return Ok(TileInfoSources::default());
|
return Ok(TileInfoSources::default());
|
||||||
};
|
};
|
||||||
let cfg = cfg.extract_file_config();
|
|
||||||
|
|
||||||
let mut results = TileInfoSources::default();
|
let mut results = TileInfoSources::default();
|
||||||
let mut configs = HashMap::new();
|
let mut configs = HashMap::new();
|
||||||
@ -202,50 +208,47 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(paths) = cfg.paths {
|
for path in cfg.paths {
|
||||||
for path in paths {
|
let is_dir = path.is_dir();
|
||||||
let is_dir = path.is_dir();
|
let dir_files = if is_dir {
|
||||||
let dir_files = if is_dir {
|
// directories will be kept in the config just in case there are new files
|
||||||
// directories will be kept in the config just in case there are new files
|
directories.push(path.clone());
|
||||||
directories.push(path.clone());
|
path.read_dir()
|
||||||
path.read_dir()
|
.map_err(|e| IoError(e, path.clone()))?
|
||||||
.map_err(|e| IoError(e, path.clone()))?
|
.filter_map(Result::ok)
|
||||||
.filter_map(Result::ok)
|
.filter(|f| {
|
||||||
.filter(|f| {
|
f.path().extension().filter(|e| *e == extension).is_some() && f.path().is_file()
|
||||||
f.path().extension().filter(|e| *e == extension).is_some()
|
})
|
||||||
&& f.path().is_file()
|
.map(|f| f.path())
|
||||||
})
|
.collect()
|
||||||
.map(|f| f.path())
|
} else if path.is_file() {
|
||||||
.collect()
|
vec![path]
|
||||||
} else if path.is_file() {
|
} else {
|
||||||
vec![path]
|
return Err(InvalidFilePath(path.canonicalize().unwrap_or(path)));
|
||||||
} else {
|
};
|
||||||
return Err(InvalidFilePath(path.canonicalize().unwrap_or(path)));
|
for path in dir_files {
|
||||||
};
|
let can = path.canonicalize().map_err(|e| IoError(e, path.clone()))?;
|
||||||
for path in dir_files {
|
if files.contains(&can) {
|
||||||
let can = path.canonicalize().map_err(|e| IoError(e, path.clone()))?;
|
if !is_dir {
|
||||||
if files.contains(&can) {
|
warn!("Ignoring duplicate MBTiles path: {}", can.display());
|
||||||
if !is_dir {
|
|
||||||
warn!("Ignoring duplicate MBTiles path: {}", can.display());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
let id = path.file_stem().map_or_else(
|
continue;
|
||||||
|| "_unknown".to_string(),
|
|
||||||
|s| s.to_string_lossy().to_string(),
|
|
||||||
);
|
|
||||||
let source = FileConfigSrc::Path(path);
|
|
||||||
let id = idr.resolve(&id, can.to_string_lossy().to_string());
|
|
||||||
info!("Configured source {id} from {}", can.display());
|
|
||||||
files.insert(can);
|
|
||||||
configs.insert(id.clone(), source.clone());
|
|
||||||
|
|
||||||
let path = match source {
|
|
||||||
FileConfigSrc::Obj(pmt) => pmt.path,
|
|
||||||
FileConfigSrc::Path(path) => path,
|
|
||||||
};
|
|
||||||
results.push(create_source(id, path).await?);
|
|
||||||
}
|
}
|
||||||
|
let id = path.file_stem().map_or_else(
|
||||||
|
|| "_unknown".to_string(),
|
||||||
|
|s| s.to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
|
let source = FileConfigSrc::Path(path);
|
||||||
|
let id = idr.resolve(&id, can.to_string_lossy().to_string());
|
||||||
|
info!("Configured source {id} from {}", can.display());
|
||||||
|
files.insert(can);
|
||||||
|
configs.insert(id.clone(), source.clone());
|
||||||
|
|
||||||
|
let path = match source {
|
||||||
|
FileConfigSrc::Obj(pmt) => pmt.path,
|
||||||
|
FileConfigSrc::Path(path) => path,
|
||||||
|
};
|
||||||
|
results.push(create_source(id, path).await?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +283,7 @@ mod tests {
|
|||||||
let FileConfigEnum::Config(cfg) = cfg else {
|
let FileConfigEnum::Config(cfg) = cfg else {
|
||||||
panic!();
|
panic!();
|
||||||
};
|
};
|
||||||
let paths = cfg.paths.clone().unwrap().into_iter().collect::<Vec<_>>();
|
let paths = cfg.paths.clone().into_iter().collect::<Vec<_>>();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
paths,
|
paths,
|
||||||
vec![
|
vec![
|
||||||
|
@ -31,7 +31,7 @@ pub use crate::args::Env;
|
|||||||
pub use crate::config::{read_config, Config, ServerState};
|
pub use crate::config::{read_config, Config, ServerState};
|
||||||
pub use crate::source::Source;
|
pub use crate::source::Source;
|
||||||
pub use crate::utils::{
|
pub use crate::utils::{
|
||||||
decode_brotli, decode_gzip, BoolOrObject, Error, IdResolver, OneOrMany, Result,
|
decode_brotli, decode_gzip, Error, IdResolver, OptBoolObj, OptOneMany, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure README.md contains valid code
|
// Ensure README.md contains valid code
|
||||||
|
@ -11,7 +11,7 @@ use crate::pg::config_table::TableInfoSources;
|
|||||||
use crate::pg::configurator::PgBuilder;
|
use crate::pg::configurator::PgBuilder;
|
||||||
use crate::pg::Result;
|
use crate::pg::Result;
|
||||||
use crate::source::TileInfoSources;
|
use crate::source::TileInfoSources;
|
||||||
use crate::utils::{on_slow, sorted_opt_map, BoolOrObject, IdResolver, OneOrMany};
|
use crate::utils::{on_slow, sorted_opt_map, IdResolver, OptBoolObj, OptOneMany};
|
||||||
|
|
||||||
pub trait PgInfo {
|
pub trait PgInfo {
|
||||||
fn format_id(&self) -> String;
|
fn format_id(&self) -> String;
|
||||||
@ -47,8 +47,8 @@ pub struct PgConfig {
|
|||||||
pub max_feature_count: Option<usize>,
|
pub max_feature_count: Option<usize>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub pool_size: Option<usize>,
|
pub pool_size: Option<usize>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptBoolObj::is_none")]
|
||||||
pub auto_publish: Option<BoolOrObject<PgCfgPublish>>,
|
pub auto_publish: OptBoolObj<PgCfgPublish>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(serialize_with = "sorted_opt_map")]
|
#[serde(serialize_with = "sorted_opt_map")]
|
||||||
pub tables: Option<TableInfoSources>,
|
pub tables: Option<TableInfoSources>,
|
||||||
@ -59,29 +59,29 @@ pub struct PgConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PgCfgPublish {
|
pub struct PgCfgPublish {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
#[serde(alias = "from_schema")]
|
#[serde(alias = "from_schema")]
|
||||||
pub from_schemas: Option<OneOrMany<String>>,
|
pub from_schemas: OptOneMany<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptBoolObj::is_none")]
|
||||||
pub tables: Option<BoolOrObject<PgCfgPublishType>>,
|
pub tables: OptBoolObj<PgCfgPublishTables>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptBoolObj::is_none")]
|
||||||
pub functions: Option<BoolOrObject<PgCfgPublishType>>,
|
pub functions: OptBoolObj<PgCfgPublishFuncs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct PgCfgPublishType {
|
pub struct PgCfgPublishTables {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
#[serde(alias = "from_schema")]
|
#[serde(alias = "from_schema")]
|
||||||
pub from_schemas: Option<OneOrMany<String>>,
|
pub from_schemas: OptOneMany<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
#[serde(alias = "id_format")]
|
#[serde(alias = "id_format")]
|
||||||
pub source_id_format: Option<String>,
|
pub source_id_format: Option<String>,
|
||||||
/// A table column to use as the feature ID
|
/// A table column to use as the feature ID
|
||||||
/// If a table has no column with this name, `id_column` will not be set for that table.
|
/// If a table has no column with this name, `id_column` will not be set for that table.
|
||||||
/// If a list of strings is given, the first found column will be treated as a feature ID.
|
/// If a list of strings is given, the first found column will be treated as a feature ID.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
#[serde(alias = "id_column")]
|
#[serde(alias = "id_column")]
|
||||||
pub id_columns: Option<OneOrMany<String>>,
|
pub id_columns: OptOneMany<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub clip_geom: Option<bool>,
|
pub clip_geom: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@ -90,6 +90,16 @@ pub struct PgCfgPublishType {
|
|||||||
pub extent: Option<u32>,
|
pub extent: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct PgCfgPublishFuncs {
|
||||||
|
#[serde(default, skip_serializing_if = "OptOneMany::is_none")]
|
||||||
|
#[serde(alias = "from_schema")]
|
||||||
|
pub from_schemas: OptOneMany<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(alias = "id_format")]
|
||||||
|
pub source_id_format: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PgConfig {
|
impl PgConfig {
|
||||||
/// Apply defaults to the config, and validate if there is a connection string
|
/// Apply defaults to the config, and validate if there is a connection string
|
||||||
pub fn finalize(&mut self) -> Result<UnrecognizedValues> {
|
pub fn finalize(&mut self) -> Result<UnrecognizedValues> {
|
||||||
@ -105,7 +115,7 @@ impl PgConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.tables.is_none() && self.functions.is_none() && self.auto_publish.is_none() {
|
if self.tables.is_none() && self.functions.is_none() && self.auto_publish.is_none() {
|
||||||
self.auto_publish = Some(BoolOrObject::Bool(true));
|
self.auto_publish = OptBoolObj::Bool(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@ -143,7 +153,7 @@ mod tests {
|
|||||||
use crate::pg::config_function::FunctionInfo;
|
use crate::pg::config_function::FunctionInfo;
|
||||||
use crate::pg::config_table::TableInfo;
|
use crate::pg::config_table::TableInfo;
|
||||||
use crate::test_utils::some;
|
use crate::test_utils::some;
|
||||||
use crate::utils::OneOrMany::{Many, One};
|
use crate::utils::OptOneMany::{Many, One};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_pg_one() {
|
fn parse_pg_one() {
|
||||||
@ -153,11 +163,11 @@ mod tests {
|
|||||||
connection_string: 'postgresql://postgres@localhost/db'
|
connection_string: 'postgresql://postgres@localhost/db'
|
||||||
"},
|
"},
|
||||||
&Config {
|
&Config {
|
||||||
postgres: Some(One(PgConfig {
|
postgres: One(PgConfig {
|
||||||
connection_string: some("postgresql://postgres@localhost/db"),
|
connection_string: some("postgresql://postgres@localhost/db"),
|
||||||
auto_publish: Some(BoolOrObject::Bool(true)),
|
auto_publish: OptBoolObj::Bool(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -172,18 +182,18 @@ mod tests {
|
|||||||
- connection_string: 'postgresql://postgres@localhost:5433/db'
|
- connection_string: 'postgresql://postgres@localhost:5433/db'
|
||||||
"},
|
"},
|
||||||
&Config {
|
&Config {
|
||||||
postgres: Some(Many(vec![
|
postgres: Many(vec![
|
||||||
PgConfig {
|
PgConfig {
|
||||||
connection_string: some("postgres://postgres@localhost:5432/db"),
|
connection_string: some("postgres://postgres@localhost:5432/db"),
|
||||||
auto_publish: Some(BoolOrObject::Bool(true)),
|
auto_publish: OptBoolObj::Bool(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
PgConfig {
|
PgConfig {
|
||||||
connection_string: some("postgresql://postgres@localhost:5433/db"),
|
connection_string: some("postgresql://postgres@localhost:5433/db"),
|
||||||
auto_publish: Some(BoolOrObject::Bool(true)),
|
auto_publish: OptBoolObj::Bool(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
])),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -225,7 +235,7 @@ mod tests {
|
|||||||
bounds: [-180.0, -90.0, 180.0, 90.0]
|
bounds: [-180.0, -90.0, 180.0, 90.0]
|
||||||
"},
|
"},
|
||||||
&Config {
|
&Config {
|
||||||
postgres: Some(One(PgConfig {
|
postgres: One(PgConfig {
|
||||||
connection_string: some("postgres://postgres@localhost:5432/db"),
|
connection_string: some("postgres://postgres@localhost:5432/db"),
|
||||||
default_srid: Some(4326),
|
default_srid: Some(4326),
|
||||||
pool_size: Some(20),
|
pool_size: Some(20),
|
||||||
@ -262,7 +272,7 @@ mod tests {
|
|||||||
),
|
),
|
||||||
)])),
|
)])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
404
martin/src/pg/configurator.rs
Executable file → Normal file
404
martin/src/pg/configurator.rs
Executable file → Normal file
@ -16,20 +16,42 @@ use crate::pg::table_source::{
|
|||||||
};
|
};
|
||||||
use crate::pg::utils::{find_info, find_kv_ignore_case, normalize_key, InfoMap};
|
use crate::pg::utils::{find_info, find_kv_ignore_case, normalize_key, InfoMap};
|
||||||
use crate::pg::PgError::InvalidTableExtent;
|
use crate::pg::PgError::InvalidTableExtent;
|
||||||
use crate::pg::Result;
|
use crate::pg::{PgCfgPublish, PgCfgPublishFuncs, Result};
|
||||||
use crate::source::TileInfoSources;
|
use crate::source::TileInfoSources;
|
||||||
use crate::utils::{BoolOrObject, IdResolver, OneOrMany};
|
use crate::utils::IdResolver;
|
||||||
|
use crate::utils::OptOneMany::NoVals;
|
||||||
|
use crate::OptBoolObj::{Bool, NoValue, Object};
|
||||||
|
|
||||||
pub type SqlFuncInfoMapMap = InfoMap<InfoMap<(PgSqlInfo, FunctionInfo)>>;
|
pub type SqlFuncInfoMapMap = InfoMap<InfoMap<(PgSqlInfo, FunctionInfo)>>;
|
||||||
pub type SqlTableInfoMapMapMap = InfoMap<InfoMap<InfoMap<TableInfo>>>;
|
pub type SqlTableInfoMapMapMap = InfoMap<InfoMap<InfoMap<TableInfo>>>;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct PgBuilderAuto {
|
#[cfg_attr(test, derive(serde::Serialize))]
|
||||||
source_id_format: String,
|
pub struct PgBuilderFuncs {
|
||||||
|
#[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
|
||||||
schemas: Option<HashSet<String>>,
|
schemas: Option<HashSet<String>>,
|
||||||
|
source_id_format: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(test, derive(serde::Serialize))]
|
||||||
|
pub struct PgBuilderTables {
|
||||||
|
#[cfg_attr(
|
||||||
|
test,
|
||||||
|
serde(
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
serialize_with = "crate::utils::sorted_opt_set"
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
schemas: Option<HashSet<String>>,
|
||||||
|
source_id_format: String,
|
||||||
|
#[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
|
||||||
id_columns: Option<Vec<String>>,
|
id_columns: Option<Vec<String>>,
|
||||||
|
#[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
|
||||||
clip_geom: Option<bool>,
|
clip_geom: Option<bool>,
|
||||||
|
#[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
|
||||||
buffer: Option<u32>,
|
buffer: Option<u32>,
|
||||||
|
#[cfg_attr(test, serde(skip_serializing_if = "Option::is_none"))]
|
||||||
extent: Option<u32>,
|
extent: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,17 +61,39 @@ pub struct PgBuilder {
|
|||||||
default_srid: Option<i32>,
|
default_srid: Option<i32>,
|
||||||
disable_bounds: bool,
|
disable_bounds: bool,
|
||||||
max_feature_count: Option<usize>,
|
max_feature_count: Option<usize>,
|
||||||
auto_functions: Option<PgBuilderAuto>,
|
auto_functions: Option<PgBuilderFuncs>,
|
||||||
auto_tables: Option<PgBuilderAuto>,
|
auto_tables: Option<PgBuilderTables>,
|
||||||
id_resolver: IdResolver,
|
id_resolver: IdResolver,
|
||||||
tables: TableInfoSources,
|
tables: TableInfoSources,
|
||||||
functions: FuncInfoSources,
|
functions: FuncInfoSources,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combine `from_schema` field from the `config.auto_publish` and `config.auto_publish.tables/functions`
|
||||||
|
macro_rules! get_auto_schemas {
|
||||||
|
($config:expr, $typ:ident) => {
|
||||||
|
if let Object(v) = &$config.auto_publish {
|
||||||
|
match (&v.from_schemas, &v.$typ) {
|
||||||
|
(NoVals, NoValue | Bool(_)) => None,
|
||||||
|
(v, NoValue | Bool(_)) => v.opt_iter().map(|v| v.cloned().collect()),
|
||||||
|
(NoVals, Object(v)) => v.from_schemas.opt_iter().map(|v| v.cloned().collect()),
|
||||||
|
(v, Object(v2)) => {
|
||||||
|
let mut vals: HashSet<_> = v.iter().cloned().collect();
|
||||||
|
vals.extend(v2.from_schemas.iter().cloned());
|
||||||
|
Some(vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl PgBuilder {
|
impl PgBuilder {
|
||||||
pub async fn new(config: &PgConfig, id_resolver: IdResolver) -> Result<Self> {
|
pub async fn new(config: &PgConfig, id_resolver: IdResolver) -> Result<Self> {
|
||||||
let pool = PgPool::new(config).await?;
|
let pool = PgPool::new(config).await?;
|
||||||
|
|
||||||
|
let (auto_tables, auto_functions) = calc_auto(config);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
pool,
|
pool,
|
||||||
default_srid: config.default_srid,
|
default_srid: config.default_srid,
|
||||||
@ -58,8 +102,8 @@ impl PgBuilder {
|
|||||||
id_resolver,
|
id_resolver,
|
||||||
tables: config.tables.clone().unwrap_or_default(),
|
tables: config.tables.clone().unwrap_or_default(),
|
||||||
functions: config.functions.clone().unwrap_or_default(),
|
functions: config.functions.clone().unwrap_or_default(),
|
||||||
auto_functions: new_auto_publish(config, true),
|
auto_functions,
|
||||||
auto_tables: new_auto_publish(config, false),
|
auto_tables,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +319,7 @@ impl PgBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_auto_fields(id: &str, inf: &mut TableInfo, auto_tables: &PgBuilderAuto) {
|
fn update_auto_fields(id: &str, inf: &mut TableInfo, auto_tables: &PgBuilderTables) {
|
||||||
if inf.clip_geom.is_none() {
|
if inf.clip_geom.is_none() {
|
||||||
inf.clip_geom = auto_tables.clip_geom;
|
inf.clip_geom = auto_tables.clip_geom;
|
||||||
}
|
}
|
||||||
@ -333,83 +377,82 @@ fn update_auto_fields(id: &str, inf: &mut TableInfo, auto_tables: &PgBuilderAuto
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_auto_publish(config: &PgConfig, is_function: bool) -> Option<PgBuilderAuto> {
|
fn calc_auto(config: &PgConfig) -> (Option<PgBuilderTables>, Option<PgBuilderFuncs>) {
|
||||||
let default_id_fmt = |is_func| (if is_func { "{function}" } else { "{table}" }).to_string();
|
let auto_tables = if use_auto_publish(config, false) {
|
||||||
let default = |schemas| {
|
let schemas = get_auto_schemas!(config, tables);
|
||||||
Some(PgBuilderAuto {
|
let bld = if let Object(PgCfgPublish {
|
||||||
source_id_format: default_id_fmt(is_function),
|
tables: Object(v), ..
|
||||||
schemas,
|
}) = &config.auto_publish
|
||||||
id_columns: None,
|
{
|
||||||
clip_geom: None,
|
PgBuilderTables {
|
||||||
buffer: None,
|
schemas,
|
||||||
extent: None,
|
source_id_format: v
|
||||||
})
|
.source_id_format
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("{table}")
|
||||||
|
.to_string(),
|
||||||
|
id_columns: v.id_columns.opt_iter().map(|v| v.cloned().collect()),
|
||||||
|
clip_geom: v.clip_geom,
|
||||||
|
buffer: v.buffer,
|
||||||
|
extent: v.extent,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PgBuilderTables {
|
||||||
|
schemas,
|
||||||
|
source_id_format: "{table}".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Some(bld)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(bo_a) = &config.auto_publish {
|
let auto_functions = if use_auto_publish(config, true) {
|
||||||
match bo_a {
|
Some(PgBuilderFuncs {
|
||||||
BoolOrObject::Object(a) => match if is_function { &a.functions } else { &a.tables } {
|
schemas: get_auto_schemas!(config, functions),
|
||||||
Some(bo_i) => match bo_i {
|
source_id_format: if let Object(PgCfgPublish {
|
||||||
BoolOrObject::Object(item) => Some(PgBuilderAuto {
|
functions:
|
||||||
source_id_format: item
|
Object(PgCfgPublishFuncs {
|
||||||
.source_id_format
|
source_id_format: Some(v),
|
||||||
.as_ref()
|
..
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| default_id_fmt(is_function)),
|
|
||||||
schemas: merge_opt_hs(&a.from_schemas, &item.from_schemas),
|
|
||||||
id_columns: item.id_columns.as_ref().and_then(|ids| {
|
|
||||||
if is_function {
|
|
||||||
error!("Configuration parameter auto_publish.functions.id_columns is not supported");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ids.iter().cloned().collect())
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
clip_geom: {
|
|
||||||
if is_function {
|
|
||||||
error!("Configuration parameter auto_publish.functions.clip_geom is not supported");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
item.clip_geom
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buffer: {
|
|
||||||
if is_function {
|
|
||||||
error!("Configuration parameter auto_publish.functions.buffer is not supported");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
item.buffer
|
|
||||||
}
|
|
||||||
},
|
|
||||||
extent: {
|
|
||||||
if is_function {
|
|
||||||
error!("Configuration parameter auto_publish.functions.extent is not supported");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
item.extent
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
BoolOrObject::Bool(true) => default(merge_opt_hs(&a.from_schemas, &None)),
|
..
|
||||||
BoolOrObject::Bool(false) => None,
|
}) = &config.auto_publish
|
||||||
},
|
{
|
||||||
|
v.clone()
|
||||||
|
} else {
|
||||||
|
"{function}".to_string()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
(auto_tables, auto_functions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn use_auto_publish(config: &PgConfig, for_functions: bool) -> bool {
|
||||||
|
match &config.auto_publish {
|
||||||
|
NoValue => config.tables.is_none() && config.functions.is_none(),
|
||||||
|
Object(funcs) => {
|
||||||
|
if for_functions {
|
||||||
// If auto_publish.functions is set, and currently asking for .tables which is missing,
|
// If auto_publish.functions is set, and currently asking for .tables which is missing,
|
||||||
// .tables becomes the inverse of functions (i.e. an obj or true in tables means false in functions)
|
// .tables becomes the inverse of functions (i.e. an obj or true in tables means false in functions)
|
||||||
None => match if is_function { &a.tables } else { &a.functions } {
|
match &funcs.functions {
|
||||||
Some(bo_i) => match bo_i {
|
NoValue => matches!(funcs.tables, NoValue | Bool(false)),
|
||||||
BoolOrObject::Object(_) | BoolOrObject::Bool(true) => None,
|
Object(_) => true,
|
||||||
BoolOrObject::Bool(false) => default(merge_opt_hs(&a.from_schemas, &None)),
|
Bool(v) => *v,
|
||||||
},
|
}
|
||||||
None => default(merge_opt_hs(&a.from_schemas, &None)),
|
} else {
|
||||||
},
|
match &funcs.tables {
|
||||||
},
|
NoValue => matches!(funcs.functions, NoValue | Bool(false)),
|
||||||
BoolOrObject::Bool(true) => default(None),
|
Object(_) => true,
|
||||||
BoolOrObject::Bool(false) => None,
|
Bool(v) => *v,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if config.tables.is_some() || config.functions.is_some() {
|
Bool(v) => *v,
|
||||||
None
|
|
||||||
} else {
|
|
||||||
default(None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,142 +485,167 @@ fn by_key<T>(a: &(String, T), b: &(String, T)) -> Ordering {
|
|||||||
a.0.cmp(&b.0)
|
a.0.cmp(&b.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merge two optional list of strings into a hashset
|
|
||||||
fn merge_opt_hs(
|
|
||||||
a: &Option<OneOrMany<String>>,
|
|
||||||
b: &Option<OneOrMany<String>>,
|
|
||||||
) -> Option<HashSet<String>> {
|
|
||||||
if let Some(a) = a {
|
|
||||||
let mut res: HashSet<_> = a.iter().cloned().collect();
|
|
||||||
if let Some(b) = b {
|
|
||||||
res.extend(b.iter().cloned());
|
|
||||||
}
|
|
||||||
Some(res)
|
|
||||||
} else {
|
|
||||||
b.as_ref().map(|b| b.iter().cloned().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use insta::assert_yaml_snapshot;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
#[derive(serde::Serialize)]
|
||||||
fn builder(source_id_format: &str, schemas: Option<&[&str]>) -> Option<PgBuilderAuto> {
|
struct AutoCfg {
|
||||||
Some(PgBuilderAuto {
|
auto_table: Option<PgBuilderTables>,
|
||||||
source_id_format: source_id_format.to_string(),
|
auto_funcs: Option<PgBuilderFuncs>,
|
||||||
schemas: schemas.map(|s| s.iter().map(|s| (*s).to_string()).collect()),
|
|
||||||
id_columns: None,
|
|
||||||
clip_geom: None,
|
|
||||||
buffer: None,
|
|
||||||
extent: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
fn auto(content: &str) -> AutoCfg {
|
||||||
fn parse_yaml(content: &str) -> PgConfig {
|
let cfg: PgConfig = serde_yaml::from_str(content).unwrap();
|
||||||
serde_yaml::from_str(content).unwrap()
|
let (auto_table, auto_funcs) = calc_auto(&cfg);
|
||||||
|
AutoCfg {
|
||||||
|
auto_table,
|
||||||
|
auto_funcs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn test_auto_publish_no_auto() {
|
fn test_auto_publish_no_auto() {
|
||||||
let config = parse_yaml("{}");
|
let cfg = auto("{}");
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(res, builder("{table}", None));
|
---
|
||||||
let res = new_auto_publish(&config, true);
|
auto_table:
|
||||||
assert_eq!(res, builder("{function}", None));
|
source_id_format: "{table}"
|
||||||
|
auto_funcs:
|
||||||
|
source_id_format: "{function}"
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml("tables: {}");
|
let cfg = auto("tables: {}");
|
||||||
assert_eq!(new_auto_publish(&config, false), None);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
---
|
||||||
|
auto_table: ~
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml("functions: {}");
|
let cfg = auto("functions: {}");
|
||||||
assert_eq!(new_auto_publish(&config, false), None);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
---
|
||||||
}
|
auto_table: ~
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
#[test]
|
let cfg = auto("auto_publish: true");
|
||||||
fn test_auto_publish_bool() {
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
let config = parse_yaml("auto_publish: true");
|
---
|
||||||
let res = new_auto_publish(&config, false);
|
auto_table:
|
||||||
assert_eq!(res, builder("{table}", None));
|
source_id_format: "{table}"
|
||||||
let res = new_auto_publish(&config, true);
|
auto_funcs:
|
||||||
assert_eq!(res, builder("{function}", None));
|
source_id_format: "{function}"
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml("auto_publish: false");
|
let cfg = auto("auto_publish: false");
|
||||||
assert_eq!(new_auto_publish(&config, false), None);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
---
|
||||||
}
|
auto_table: ~
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
#[test]
|
let cfg = auto(indoc! {"
|
||||||
fn test_auto_publish_obj_bool() {
|
|
||||||
let config = parse_yaml(indoc! {"
|
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
tables: true"});
|
tables: true"});
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(res, builder("{table}", Some(&["public"])));
|
---
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
auto_table:
|
||||||
|
schemas:
|
||||||
|
- public
|
||||||
|
source_id_format: "{table}"
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml(indoc! {"
|
let cfg = auto(indoc! {"
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
functions: true"});
|
functions: true"});
|
||||||
assert_eq!(new_auto_publish(&config, false), None);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
let res = new_auto_publish(&config, true);
|
---
|
||||||
assert_eq!(res, builder("{function}", Some(&["public"])));
|
auto_table: ~
|
||||||
|
auto_funcs:
|
||||||
|
schemas:
|
||||||
|
- public
|
||||||
|
source_id_format: "{function}"
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml(indoc! {"
|
let cfg = auto(indoc! {"
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
tables: false"});
|
tables: false"});
|
||||||
assert_eq!(new_auto_publish(&config, false), None);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
let res = new_auto_publish(&config, true);
|
---
|
||||||
assert_eq!(res, builder("{function}", Some(&["public"])));
|
auto_table: ~
|
||||||
|
auto_funcs:
|
||||||
|
schemas:
|
||||||
|
- public
|
||||||
|
source_id_format: "{function}"
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml(indoc! {"
|
let cfg = auto(indoc! {"
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
functions: false"});
|
functions: false"});
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(res, builder("{table}", Some(&["public"])));
|
---
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
auto_table:
|
||||||
}
|
schemas:
|
||||||
|
- public
|
||||||
|
source_id_format: "{table}"
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
#[test]
|
let cfg = auto(indoc! {"
|
||||||
fn test_auto_publish_obj_obj() {
|
|
||||||
let config = parse_yaml(indoc! {"
|
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
tables:
|
tables:
|
||||||
from_schemas: osm
|
from_schemas: osm
|
||||||
id_format: 'foo_{schema}.{table}_bar'"});
|
id_format: 'foo_{schema}.{table}_bar'"});
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(
|
---
|
||||||
res,
|
auto_table:
|
||||||
builder("foo_{schema}.{table}_bar", Some(&["public", "osm"]))
|
schemas:
|
||||||
);
|
- osm
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
- public
|
||||||
|
source_id_format: "foo_{schema}.{table}_bar"
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml(indoc! {"
|
let cfg = auto(indoc! {"
|
||||||
auto_publish:
|
auto_publish:
|
||||||
from_schemas: public
|
from_schemas: public
|
||||||
tables:
|
tables:
|
||||||
from_schemas: osm
|
from_schemas: osm
|
||||||
source_id_format: '{schema}.{table}'"});
|
source_id_format: '{schema}.{table}'"});
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(res, builder("{schema}.{table}", Some(&["public", "osm"])));
|
---
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
auto_table:
|
||||||
|
schemas:
|
||||||
|
- osm
|
||||||
|
- public
|
||||||
|
source_id_format: "{schema}.{table}"
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
|
|
||||||
let config = parse_yaml(indoc! {"
|
let cfg = auto(indoc! {"
|
||||||
auto_publish:
|
auto_publish:
|
||||||
tables:
|
tables:
|
||||||
from_schemas:
|
from_schemas:
|
||||||
- osm
|
- osm
|
||||||
- public"});
|
- public"});
|
||||||
let res = new_auto_publish(&config, false);
|
assert_yaml_snapshot!(cfg, @r###"
|
||||||
assert_eq!(res, builder("{table}", Some(&["public", "osm"])));
|
---
|
||||||
assert_eq!(new_auto_publish(&config, true), None);
|
auto_table:
|
||||||
|
schemas:
|
||||||
|
- osm
|
||||||
|
- public
|
||||||
|
source_id_format: "{table}"
|
||||||
|
auto_funcs: ~
|
||||||
|
"###);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,9 @@ mod table_source;
|
|||||||
mod tls;
|
mod tls;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use config::{PgCfgPublish, PgCfgPublishType, PgConfig, PgSslCerts};
|
pub use config::{PgCfgPublish, PgCfgPublishFuncs, PgCfgPublishTables, PgConfig, PgSslCerts};
|
||||||
pub use config_function::FunctionInfo;
|
pub use config_function::FunctionInfo;
|
||||||
pub use config_table::TableInfo;
|
pub use config_table::TableInfo;
|
||||||
pub use errors::{PgError, Result};
|
pub use errors::{PgError, Result};
|
||||||
pub use function_source::query_available_function;
|
pub use function_source::query_available_function;
|
||||||
pub use pool::{PgPool, POOL_SIZE_DEFAULT};
|
pub use pool::{PgPool, POOL_SIZE_DEFAULT};
|
||||||
|
|
||||||
pub use crate::utils::BoolOrObject;
|
|
||||||
|
@ -55,12 +55,11 @@ pub type SpriteCatalog = BTreeMap<String, CatalogSpriteEntry>;
|
|||||||
pub struct SpriteSources(HashMap<String, SpriteSource>);
|
pub struct SpriteSources(HashMap<String, SpriteSource>);
|
||||||
|
|
||||||
impl SpriteSources {
|
impl SpriteSources {
|
||||||
pub fn resolve(config: &mut Option<FileConfigEnum>) -> Result<Self, FileError> {
|
pub fn resolve(config: &mut FileConfigEnum) -> Result<Self, FileError> {
|
||||||
let Some(cfg) = config else {
|
let Some(cfg) = config.extract_file_config() else {
|
||||||
return Ok(Self::default());
|
return Ok(Self::default());
|
||||||
};
|
};
|
||||||
|
|
||||||
let cfg = cfg.extract_file_config();
|
|
||||||
let mut results = Self::default();
|
let mut results = Self::default();
|
||||||
let mut directories = Vec::new();
|
let mut directories = Vec::new();
|
||||||
let mut configs = HashMap::new();
|
let mut configs = HashMap::new();
|
||||||
@ -72,18 +71,16 @@ impl SpriteSources {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(paths) = cfg.paths {
|
for path in cfg.paths {
|
||||||
for path in paths {
|
let Some(name) = path.file_name() else {
|
||||||
let Some(name) = path.file_name() else {
|
warn!(
|
||||||
warn!(
|
"Ignoring sprite source with no name from {}",
|
||||||
"Ignoring sprite source with no name from {}",
|
path.display()
|
||||||
path.display()
|
);
|
||||||
);
|
continue;
|
||||||
continue;
|
};
|
||||||
};
|
directories.push(path.clone());
|
||||||
directories.push(path.clone());
|
results.add_source(name.to_string_lossy().to_string(), path);
|
||||||
results.add_source(name.to_string_lossy().to_string(), path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*config = FileConfigEnum::new_extended(directories, configs, cfg.unrecognized);
|
*config = FileConfigEnum::new_extended(directories, configs, cfg.unrecognized);
|
||||||
|
143
martin/src/utils/cfg_containers.rs
Normal file
143
martin/src/utils/cfg_containers.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
use std::vec::IntoIter;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A serde helper to store a boolean as an object.
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum OptBoolObj<T> {
|
||||||
|
#[default]
|
||||||
|
#[serde(skip)]
|
||||||
|
NoValue,
|
||||||
|
Bool(bool),
|
||||||
|
Object(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OptBoolObj<T> {
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::NoValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum OptOneMany<T> {
|
||||||
|
#[default]
|
||||||
|
NoVals,
|
||||||
|
One(T),
|
||||||
|
Many(Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for OptOneMany<T> {
|
||||||
|
type Item = T;
|
||||||
|
type IntoIter = IntoIter<T>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => Vec::new().into_iter(),
|
||||||
|
Self::One(v) => vec![v].into_iter(),
|
||||||
|
Self::Many(v) => v.into_iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OptOneMany<T> {
|
||||||
|
pub fn new<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
|
let mut iter = iter.into_iter();
|
||||||
|
match (iter.next(), iter.next()) {
|
||||||
|
(Some(first), Some(second)) => {
|
||||||
|
let mut vec = Vec::with_capacity(iter.size_hint().0 + 2);
|
||||||
|
vec.push(first);
|
||||||
|
vec.push(second);
|
||||||
|
vec.extend(iter);
|
||||||
|
Self::Many(vec)
|
||||||
|
}
|
||||||
|
(Some(first), None) => Self::One(first),
|
||||||
|
(None, _) => Self::NoVals,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
matches!(self, Self::NoVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => true,
|
||||||
|
Self::One(_) => false,
|
||||||
|
Self::Many(v) => v.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => [].iter(),
|
||||||
|
Self::One(v) => std::slice::from_ref(v).iter(),
|
||||||
|
Self::Many(v) => v.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opt_iter(&self) -> Option<impl Iterator<Item = &T>> {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => None,
|
||||||
|
Self::One(v) => Some(std::slice::from_ref(v).iter()),
|
||||||
|
Self::Many(v) => Some(v.iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => [].iter_mut(),
|
||||||
|
Self::One(v) => std::slice::from_mut(v).iter_mut(),
|
||||||
|
Self::Many(v) => v.iter_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[T] {
|
||||||
|
match self {
|
||||||
|
Self::NoVals => &[],
|
||||||
|
Self::One(item) => std::slice::from_ref(item),
|
||||||
|
Self::Many(v) => v.as_slice(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::OptOneMany::{Many, NoVals, One};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_one_or_many() {
|
||||||
|
let mut noval: OptOneMany<i32> = NoVals;
|
||||||
|
let mut one = One(1);
|
||||||
|
let mut many = Many(vec![1, 2, 3]);
|
||||||
|
|
||||||
|
assert_eq!(OptOneMany::new(vec![1, 2, 3]), Many(vec![1, 2, 3]));
|
||||||
|
assert_eq!(OptOneMany::new(vec![1]), One(1));
|
||||||
|
assert_eq!(OptOneMany::new(Vec::<i32>::new()), NoVals);
|
||||||
|
|
||||||
|
assert_eq!(noval.iter_mut().collect::<Vec<_>>(), Vec::<&i32>::new());
|
||||||
|
assert_eq!(one.iter_mut().collect::<Vec<_>>(), vec![&1]);
|
||||||
|
assert_eq!(many.iter_mut().collect::<Vec<_>>(), vec![&1, &2, &3]);
|
||||||
|
|
||||||
|
assert_eq!(noval.iter().collect::<Vec<_>>(), Vec::<&i32>::new());
|
||||||
|
assert_eq!(one.iter().collect::<Vec<_>>(), vec![&1]);
|
||||||
|
assert_eq!(many.iter().collect::<Vec<_>>(), vec![&1, &2, &3]);
|
||||||
|
|
||||||
|
assert_eq!(noval.opt_iter().map(Iterator::collect::<Vec<_>>), None);
|
||||||
|
assert_eq!(one.opt_iter().map(Iterator::collect), Some(vec![&1]));
|
||||||
|
assert_eq!(
|
||||||
|
many.opt_iter().map(Iterator::collect),
|
||||||
|
Some(vec![&1, &2, &3])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(noval.as_slice(), Vec::<i32>::new().as_slice());
|
||||||
|
assert_eq!(one.as_slice(), &[1]);
|
||||||
|
assert_eq!(many.as_slice(), &[1, 2, 3]);
|
||||||
|
|
||||||
|
assert_eq!(noval.into_iter().collect::<Vec<_>>(), Vec::<i32>::new());
|
||||||
|
assert_eq!(one.into_iter().collect::<Vec<_>>(), vec![1]);
|
||||||
|
assert_eq!(many.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
|
mod cfg_containers;
|
||||||
mod error;
|
mod error;
|
||||||
mod id_resolver;
|
mod id_resolver;
|
||||||
mod one_or_many;
|
|
||||||
mod utilities;
|
mod utilities;
|
||||||
mod xyz;
|
mod xyz;
|
||||||
|
|
||||||
|
pub use cfg_containers::{OptBoolObj, OptOneMany};
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use id_resolver::IdResolver;
|
pub use id_resolver::IdResolver;
|
||||||
pub use one_or_many::OneOrMany;
|
|
||||||
pub use utilities::*;
|
pub use utilities::*;
|
||||||
pub use xyz::Xyz;
|
pub use xyz::Xyz;
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
use std::vec::IntoIter;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum OneOrMany<T> {
|
|
||||||
One(T),
|
|
||||||
Many(Vec<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> IntoIterator for OneOrMany<T> {
|
|
||||||
type Item = T;
|
|
||||||
type IntoIter = IntoIter<T>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
match self {
|
|
||||||
Self::One(v) => vec![v].into_iter(),
|
|
||||||
Self::Many(v) => v.into_iter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone> OneOrMany<T> {
|
|
||||||
pub fn new_opt<I: IntoIterator<Item = T>>(iter: I) -> Option<Self> {
|
|
||||||
let mut iter = iter.into_iter();
|
|
||||||
match (iter.next(), iter.next()) {
|
|
||||||
(Some(first), Some(second)) => {
|
|
||||||
let mut vec = Vec::with_capacity(iter.size_hint().0 + 2);
|
|
||||||
vec.push(first);
|
|
||||||
vec.push(second);
|
|
||||||
vec.extend(iter);
|
|
||||||
Some(Self::Many(vec))
|
|
||||||
}
|
|
||||||
(Some(first), None) => Some(Self::One(first)),
|
|
||||||
(None, _) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::One(_) => false,
|
|
||||||
Self::Many(v) => v.is_empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
|
||||||
match self {
|
|
||||||
OneOrMany::Many(v) => v.iter(),
|
|
||||||
OneOrMany::One(v) => std::slice::from_ref(v).iter(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
|
||||||
match self {
|
|
||||||
Self::Many(v) => v.iter_mut(),
|
|
||||||
Self::One(v) => std::slice::from_mut(v).iter_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_slice(&self) -> &[T] {
|
|
||||||
match self {
|
|
||||||
Self::One(item) => std::slice::from_ref(item),
|
|
||||||
Self::Many(v) => v.as_slice(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::OneOrMany::{Many, One};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_one_or_many() {
|
|
||||||
let mut one = One(1);
|
|
||||||
let mut many = Many(vec![1, 2, 3]);
|
|
||||||
|
|
||||||
assert_eq!(OneOrMany::new_opt(vec![1, 2, 3]), Some(Many(vec![1, 2, 3])));
|
|
||||||
assert_eq!(OneOrMany::new_opt(vec![1]), Some(One(1)));
|
|
||||||
assert_eq!(OneOrMany::new_opt(Vec::<i32>::new()), None);
|
|
||||||
|
|
||||||
assert_eq!(one.iter_mut().collect::<Vec<_>>(), vec![&1]);
|
|
||||||
assert_eq!(many.iter_mut().collect::<Vec<_>>(), vec![&1, &2, &3]);
|
|
||||||
|
|
||||||
assert_eq!(one.iter().collect::<Vec<_>>(), vec![&1]);
|
|
||||||
assert_eq!(many.iter().collect::<Vec<_>>(), vec![&1, &2, &3]);
|
|
||||||
|
|
||||||
assert_eq!(one.as_slice(), &[1]);
|
|
||||||
assert_eq!(many.as_slice(), &[1, 2, 3]);
|
|
||||||
|
|
||||||
assert_eq!(one.into_iter().collect::<Vec<_>>(), vec![1]);
|
|
||||||
assert_eq!(many.into_iter().collect::<Vec<_>>(), vec![1, 2, 3]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,17 +6,9 @@ use std::time::Duration;
|
|||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use flate2::write::GzEncoder;
|
use flate2::write::GzEncoder;
|
||||||
use futures::pin_mut;
|
use futures::pin_mut;
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
/// A serde helper to store a boolean as an object.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum BoolOrObject<T> {
|
|
||||||
Bool(bool),
|
|
||||||
Object(T),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sort an optional hashmap by key, case-insensitive first, then case-sensitive
|
/// Sort an optional hashmap by key, case-insensitive first, then case-sensitive
|
||||||
pub fn sorted_opt_map<S: Serializer, T: Serialize>(
|
pub fn sorted_opt_map<S: Serializer, T: Serialize>(
|
||||||
value: &Option<HashMap<String, T>>,
|
value: &Option<HashMap<String, T>>,
|
||||||
@ -31,6 +23,21 @@ pub fn sorted_btree_map<K: Serialize + Ord, V>(value: &HashMap<K, V>) -> BTreeMa
|
|||||||
BTreeMap::from_iter(items)
|
BTreeMap::from_iter(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn sorted_opt_set<S: Serializer>(
|
||||||
|
value: &Option<std::collections::HashSet<String>>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error> {
|
||||||
|
value
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| {
|
||||||
|
let mut v: Vec<_> = v.iter().collect();
|
||||||
|
v.sort();
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.serialize(serializer)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decode_gzip(data: &[u8]) -> Result<Vec<u8>, std::io::Error> {
|
pub fn decode_gzip(data: &[u8]) -> Result<Vec<u8>, std::io::Error> {
|
||||||
let mut decoder = GzDecoder::new(data);
|
let mut decoder = GzDecoder::new(data);
|
||||||
let mut decompressed = Vec::new();
|
let mut decompressed = Vec::new();
|
||||||
|
@ -4,7 +4,7 @@ use actix_web::test::{call_and_read_body_json, call_service, read_body, TestRequ
|
|||||||
use ctor::ctor;
|
use ctor::ctor;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use insta::assert_yaml_snapshot;
|
use insta::assert_yaml_snapshot;
|
||||||
use martin::OneOrMany;
|
use martin::OptOneMany;
|
||||||
use tilejson::TileJSON;
|
use tilejson::TileJSON;
|
||||||
|
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
@ -1092,7 +1092,7 @@ tables:
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let OneOrMany::One(cfg) = cfg.postgres.unwrap() else {
|
let OptOneMany::One(cfg) = cfg.postgres else {
|
||||||
panic!()
|
panic!()
|
||||||
};
|
};
|
||||||
for (name, _) in cfg.tables.unwrap_or_default() {
|
for (name, _) in cfg.tables.unwrap_or_default() {
|
||||||
|
@ -34,8 +34,6 @@ pub fn table<'a>(mock: &'a MockSource, name: &str) -> &'a TableInfo {
|
|||||||
let (_, config) = mock;
|
let (_, config) = mock;
|
||||||
let vals: Vec<&TableInfo> = config
|
let vals: Vec<&TableInfo> = config
|
||||||
.postgres
|
.postgres
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|v| v.tables.iter().map(|vv| vv.get(name)))
|
.flat_map(|v| v.tables.iter().map(|vv| vv.get(name)))
|
||||||
.flatten()
|
.flatten()
|
||||||
|
Loading…
Reference in New Issue
Block a user