Codegen not depends on sea-schema & sqlx

This commit is contained in:
Billy Chan 2021-07-26 17:13:54 +08:00 committed by Chris Tsang
parent 6224bb6d5f
commit 0b10d30c64
22 changed files with 142 additions and 591 deletions

View File

@ -5,7 +5,6 @@ members = [
"sea-orm-codegen",
"sea-orm-cli",
"examples/sqlx-mysql",
"examples/codegen",
"examples/cli",
]

View File

@ -1,2 +1,2 @@
DATABASE_URI=mysql://sea:sea@localhost/bakery
DATABASE_URL=mysql://sea:sea@localhost/bakery
DATABASE_SCHEMA=bakery

View File

@ -1,14 +0,0 @@
[package]
name = "sea-orm-codegen-example"
version = "0.1.0"
edition = "2018"
publish = false
[dependencies]
async-std = { version = "^1.9", features = ["attributes"] }
sea-orm = { path = "../../" }
sea-orm-codegen = { path = "../../sea-orm-codegen" }
sea-query = { version = "~0.12.8" }
strum = { version = "^0.20", features = ["derive"] }
serde_json = { version = "^1" }
quote = { version = "^1" }

View File

@ -1,12 +0,0 @@
# SeaORM Entity Generator Example
Prepare:
Setup a test database and configure the connection string in `main.rs`.
Run `bakery.sql` to setup the test table and data.
Running:
```sh
cargo run
```

View File

@ -1,75 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"cake"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub id: i32,
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Id,
Name,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
Id,
}
impl PrimaryKeyTrait for PrimaryKey {
fn auto_increment() -> bool {
true
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Fruit,
}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(Some(255u32)).def(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Fruit => Entity::has_many(super::fruit::Entity).into(),
}
}
}
impl Related<super::fruit::Entity> for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Filling.def()
}
fn via() -> Option<RelationDef> {
Some(super::cake_filling::Relation::Cake.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,81 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"cake_filling"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub cake_id: i32,
pub filling_id: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
CakeId,
FillingId,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
CakeId,
FillingId,
}
impl PrimaryKeyTrait for PrimaryKey {
fn auto_increment() -> bool {
false
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Cake,
Filling,
}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::CakeId => ColumnType::Integer.def(),
Self::FillingId => ColumnType::Integer.def(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Cake => Entity::belongs_to(super::cake::Entity)
.from(Column::CakeId)
.to(super::cake::Column::Id)
.into(),
Self::Filling => Entity::belongs_to(super::filling::Entity)
.from(Column::FillingId)
.to(super::filling::Column::Id)
.into(),
}
}
}
impl Related<super::cake::Entity> for Entity {
fn to() -> RelationDef {
Relation::Cake.def()
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
Relation::Filling.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,67 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"filling"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub id: i32,
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Id,
Name,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
Id,
}
impl PrimaryKeyTrait for PrimaryKey {
fn auto_increment() -> bool {
true
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(Some(255u32)).def(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => panic!("No RelationDef"),
}
}
}
impl Related<super::cake::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Cake.def()
}
fn via() -> Option<RelationDef> {
Some(super::cake_filling::Relation::Filling.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,80 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"fruit"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub id: i32,
pub name: String,
pub cake_id: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Id,
Name,
CakeId,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
Id,
}
impl PrimaryKeyTrait for PrimaryKey {
fn auto_increment() -> bool {
true
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Cake,
Vendor,
}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(Some(255u32)).def(),
Self::CakeId => ColumnType::Integer.def().null(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Cake => Entity::belongs_to(super::cake::Entity)
.from(Column::CakeId)
.to(super::cake::Column::Id)
.into(),
Self::Vendor => Entity::has_many(super::vendor::Entity).into(),
}
}
}
impl Related<super::cake::Entity> for Entity {
fn to() -> RelationDef {
Relation::Cake.def()
}
}
impl Related<super::vendor::Entity> for Entity {
fn to() -> RelationDef {
Relation::Vendor.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,7 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
pub mod cake;
pub mod cake_filling;
pub mod filling;
pub mod fruit;
pub mod vendor;

View File

@ -1,7 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
pub use super::cake::Entity as Cake;
pub use super::cake_filling::Entity as CakeFilling;
pub use super::filling::Entity as Filling;
pub use super::fruit::Entity as Fruit;
pub use super::vendor::Entity as Vendor;

View File

@ -1,72 +0,0 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.1.0
use sea_orm::entity::prelude::*;
#[derive(Copy, Clone, Default, Debug, DeriveEntity)]
pub struct Entity;
impl EntityName for Entity {
fn table_name(&self) -> &str {
"vendor"
}
}
#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]
pub struct Model {
pub id: i32,
pub name: String,
pub fruit_id: Option<i32>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Id,
Name,
FruitId,
}
#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)]
pub enum PrimaryKey {
Id,
}
impl PrimaryKeyTrait for PrimaryKey {
fn auto_increment() -> bool {
true
}
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Fruit,
}
impl ColumnTrait for Column {
type EntityName = Entity;
fn def(&self) -> ColumnDef {
match self {
Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(Some(255u32)).def(),
Self::FruitId => ColumnType::Integer.def().null(),
}
}
}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Fruit => Entity::belongs_to(super::fruit::Entity)
.from(Column::FruitId)
.to(super::fruit::Column::Id)
.into(),
}
}
}
impl Related<super::fruit::Entity> for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,16 +0,0 @@
mod entity;
use sea_orm_codegen::{EntityGenerator, Error};
#[async_std::main]
async fn main() -> Result<(), Error> {
let uri = "mysql://sea:sea@localhost/bakery";
let schema = "bakery";
let _generator = EntityGenerator::discover(uri, schema)
.await?
.transform()?
.generate("src/entity")?;
Ok(())
}

View File

@ -20,3 +20,36 @@ clap = { version = "^2.33.3" }
dotenv = { version = "^0.15" }
async-std = { version = "^1.9", features = [ "attributes" ] }
sea-orm-codegen = { path = "../sea-orm-codegen" }
sea-schema = { version = "^0.2", default-features = false, features = [
"sqlx-mysql",
"discovery",
"writer",
] }
sqlx = { version = "^0.5", default-features = false, features = [ "mysql" ] }
[features]
default = [ "runtime-async-std-native-tls" ]
runtime-actix-native-tls = [
"sqlx/runtime-actix-native-tls",
"sea-schema/runtime-actix-native-tls",
]
runtime-async-std-native-tls = [
"sqlx/runtime-async-std-native-tls",
"sea-schema/runtime-async-std-native-tls",
]
runtime-tokio-native-tls = [
"sqlx/runtime-tokio-native-tls",
"sea-schema/runtime-tokio-native-tls",
]
runtime-actix-rustls = [
"sqlx/runtime-actix-rustls",
"sea-schema/runtime-actix-rustls",
]
runtime-async-std-rustls = [
"sqlx/runtime-async-std-rustls",
"sea-schema/runtime-async-std-rustls",
]
runtime-tokio-rustls = [
"sqlx/runtime-tokio-rustls",
"sea-schema/runtime-tokio-rustls",
]

View File

@ -9,5 +9,5 @@ cargo run -- -h
Running Entity Generator:
```sh
cargo run -- entity generate -u mysql://sea:sea@localhost/bakery -s bakery -o out
cargo run -- entity generate -url mysql://sea:sea@localhost/bakery -schema bakery -o out
```

View File

@ -8,18 +8,18 @@ pub fn build_cli() -> App<'static, 'static> {
SubCommand::with_name("entity")
.about("Generate entity")
.arg(
Arg::with_name("DATABASE_URI")
.long("uri")
.short("u")
.help("Database URI")
Arg::with_name("DATABASE_URL")
.long("database-url")
.short("url")
.help("Database URL")
.takes_value(true)
.required(true)
.env("DATABASE_URI"),
.env("DATABASE_URL"),
)
.arg(
Arg::with_name("DATABASE_SCHEMA")
.long("schema")
.short("s")
.long("database-schema")
.short("schema")
.help("Database schema")
.takes_value(true)
.required(true)

View File

@ -1,7 +1,9 @@
use clap::ArgMatches;
use dotenv::dotenv;
use sea_orm_codegen::EntityGenerator;
use std::{error::Error, fmt::Display};
use sea_orm_codegen::{EntityTransformer, OutputFile};
use sea_schema::mysql::discovery::SchemaDiscovery;
use sqlx::MySqlPool;
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command};
mod cli;
@ -22,13 +24,35 @@ async fn main() {
async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
match matches.subcommand() {
("entity", Some(args)) => {
let uri = args.value_of("DATABASE_URI").unwrap();
let url = args.value_of("DATABASE_URL").unwrap();
let schema = args.value_of("DATABASE_SCHEMA").unwrap();
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
EntityGenerator::discover(uri, schema)
.await?
.transform()?
.generate(output_dir)?;
let connection = MySqlPool::connect(url).await?;
let schema_discovery = SchemaDiscovery::new(connection, schema);
let schema = schema_discovery.discover().await;
let table_stmts = schema
.tables
.into_iter()
.map(|schema| schema.write())
.collect();
let output = EntityTransformer::transform(table_stmts)?.generate();
let dir = Path::new(output_dir);
fs::create_dir_all(dir)?;
for OutputFile { name, content } in output.files.iter() {
let file_path = dir.join(name);
let mut file = fs::File::create(file_path)?;
file.write_all(content.as_bytes())?;
}
for OutputFile { name, .. } in output.files.iter() {
Command::new("rustfmt")
.arg(dir.join(name))
.spawn()?
.wait()?;
}
}
_ => unreachable!("You should never see this message"),
};

View File

@ -16,13 +16,7 @@ name = "sea_orm_codegen"
path = "src/lib.rs"
[dependencies]
sea-schema = { version = "^0.2", default-features = false, features = [
"sqlx-mysql",
"discovery",
"writer",
] }
sea-query = { version = "~0.12.8" }
sqlx = { version = "^0.5", default-features = false, features = ["mysql"] }
syn = { version = "^1", default-features = false, features = [
"derive",
"parsing",
@ -36,29 +30,3 @@ proc-macro2 = "^1"
[dev-dependencies]
async-std = { version = "^1.9", features = ["attributes"] }
sea-orm = { path = "../" }
[features]
runtime-actix-native-tls = [
"sqlx/runtime-actix-native-tls",
"sea-schema/runtime-actix-native-tls",
]
runtime-async-std-native-tls = [
"sqlx/runtime-async-std-native-tls",
"sea-schema/runtime-async-std-native-tls",
]
runtime-tokio-native-tls = [
"sqlx/runtime-tokio-native-tls",
"sea-schema/runtime-tokio-native-tls",
]
runtime-actix-rustls = [
"sqlx/runtime-actix-rustls",
"sea-schema/runtime-actix-rustls",
]
runtime-async-std-rustls = [
"sqlx/runtime-async-std-rustls",
"sea-schema/runtime-async-std-rustls",
]
runtime-tokio-rustls = [
"sqlx/runtime-tokio-rustls",
"sea-schema/runtime-tokio-rustls",
]

View File

@ -1,15 +0,0 @@
use crate::{EntityTransformer, Error};
use sea_schema::mysql::discovery::SchemaDiscovery;
use sqlx::MySqlPool;
#[derive(Clone, Debug)]
pub struct EntityGenerator {}
impl EntityGenerator {
pub async fn discover(uri: &str, schema: &str) -> Result<EntityTransformer, Error> {
let connection = MySqlPool::connect(uri).await?;
let schema_discovery = SchemaDiscovery::new(connection, schema);
let schema = schema_discovery.discover().await;
Ok(EntityTransformer { schema })
}
}

View File

@ -1,7 +1,6 @@
mod base_entity;
mod column;
mod conjunct_relation;
mod generator;
mod primary_key;
mod relation;
mod transformer;
@ -10,7 +9,6 @@ mod writer;
pub use base_entity::*;
pub use column::*;
pub use conjunct_relation::*;
pub use generator::*;
pub use primary_key::*;
pub use relation::*;
pub use transformer::*;

View File

@ -2,21 +2,17 @@ use crate::{
Column, ConjunctRelation, Entity, EntityWriter, Error, PrimaryKey, Relation, RelationType,
};
use sea_query::TableStatement;
use sea_schema::mysql::def::Schema;
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub struct EntityTransformer {
pub(crate) schema: Schema,
}
pub struct EntityTransformer;
impl EntityTransformer {
pub fn transform(self) -> Result<EntityWriter, Error> {
pub fn transform(table_stmts: Vec<TableStatement>) -> Result<EntityWriter, Error> {
let mut inverse_relations: HashMap<String, Vec<Relation>> = HashMap::new();
let mut conjunct_relations: HashMap<String, Vec<ConjunctRelation>> = HashMap::new();
let mut entities = HashMap::new();
for table_ref in self.schema.tables.iter() {
let table_stmt = table_ref.write();
for table_stmt in table_stmts.into_iter() {
let table_create = match table_stmt {
TableStatement::Create(stmt) => stmt,
_ => {

View File

@ -1,105 +1,93 @@
use crate::{Entity, Error};
use crate::Entity;
use proc_macro2::TokenStream;
use quote::quote;
use std::{
fs::{self, File},
io::{self, Write},
path::Path,
process::Command,
};
#[derive(Clone, Debug)]
pub struct EntityWriter {
pub(crate) entities: Vec<Entity>,
}
pub struct WriterOutput {
pub files: Vec<OutputFile>,
}
pub struct OutputFile {
pub name: String,
pub content: String,
}
impl EntityWriter {
pub fn generate(self, output_dir: &str) -> Result<(), Error> {
for entity in self.entities.iter() {
let code_blocks = Self::gen_code_blocks(entity);
Self::write(output_dir, entity, code_blocks)?;
}
for entity in self.entities.iter() {
Self::format_entity(output_dir, entity)?;
}
self.write_mod(output_dir)?;
self.write_prelude(output_dir)?;
Ok(())
pub fn generate(self) -> WriterOutput {
let mut files = Vec::new();
files.extend(self.write_entities());
files.push(self.write_mod());
files.push(self.write_prelude());
WriterOutput { files }
}
pub fn write_mod(&self, output_dir: &str) -> io::Result<()> {
let file_name = "mod.rs";
let dir = Self::create_dir(output_dir)?;
let file_path = dir.join(file_name);
let mut file = fs::File::create(file_path)?;
Self::write_doc_comment(&mut file)?;
for entity in self.entities.iter() {
let code_block = Self::gen_mod(entity);
file.write_all(code_block.to_string().as_bytes())?;
}
Self::format_file(output_dir, file_name)?;
Ok(())
pub fn write_entities(&self) -> Vec<OutputFile> {
self.entities
.iter()
.map(|entity| {
let mut lines = Vec::new();
Self::write_doc_comment(&mut lines);
let code_blocks = Self::gen_code_blocks(entity);
Self::write(&mut lines, code_blocks);
OutputFile {
name: format!("{}.rs", entity.table_name),
content: lines.join("\n\n"),
}
})
.collect()
}
pub fn write_prelude(&self, output_dir: &str) -> io::Result<()> {
let file_name = "prelude.rs";
let dir = Self::create_dir(output_dir)?;
let file_path = dir.join(file_name);
let mut file = fs::File::create(file_path)?;
Self::write_doc_comment(&mut file)?;
for entity in self.entities.iter() {
let code_block = Self::gen_prelude_use(entity);
file.write_all(code_block.to_string().as_bytes())?;
pub fn write_mod(&self) -> OutputFile {
let mut lines = Vec::new();
Self::write_doc_comment(&mut lines);
let code_blocks = self
.entities
.iter()
.map(|entity| Self::gen_mod(entity))
.collect();
Self::write(&mut lines, code_blocks);
OutputFile {
name: "mod.rs".to_owned(),
content: lines.join("\n"),
}
Self::format_file(output_dir, file_name)?;
Ok(())
}
pub fn write(
output_dir: &str,
entity: &Entity,
code_blocks: Vec<TokenStream>,
) -> io::Result<()> {
let dir = Self::create_dir(output_dir)?;
let file_path = dir.join(format!("{}.rs", entity.table_name));
let mut file = fs::File::create(file_path)?;
Self::write_doc_comment(&mut file)?;
for code_block in code_blocks {
file.write_all(code_block.to_string().as_bytes())?;
file.write_all(b"\n\n")?;
pub fn write_prelude(&self) -> OutputFile {
let mut lines = Vec::new();
Self::write_doc_comment(&mut lines);
let code_blocks = self
.entities
.iter()
.map(|entity| Self::gen_prelude_use(entity))
.collect();
Self::write(&mut lines, code_blocks);
OutputFile {
name: "prelude.rs".to_owned(),
content: lines.join("\n"),
}
Ok(())
}
pub fn write_doc_comment(file: &mut File) -> io::Result<()> {
pub fn write(lines: &mut Vec<String>, code_blocks: Vec<TokenStream>) {
lines.extend(
code_blocks
.into_iter()
.map(|code_block| code_block.to_string())
.collect::<Vec<_>>(),
);
}
pub fn write_doc_comment(lines: &mut Vec<String>) {
let ver = env!("CARGO_PKG_VERSION");
let comments = vec![format!(
"//! SeaORM Entity. Generated by sea-orm-codegen {}",
ver
)];
for comment in comments {
file.write_all(comment.as_bytes())?;
file.write_all(b"\n\n")?;
}
Ok(())
}
pub fn create_dir(output_dir: &str) -> io::Result<&Path> {
let dir = Path::new(output_dir);
fs::create_dir_all(dir)?;
Ok(dir)
}
pub fn format_entity(output_dir: &str, entity: &Entity) -> io::Result<()> {
Self::format_file(output_dir, &format!("{}.rs", entity.table_name))
}
pub fn format_file(output_dir: &str, file_name: &str) -> io::Result<()> {
Command::new("rustfmt")
.arg(Path::new(output_dir).join(file_name))
.spawn()?
.wait()?;
Ok(())
lines.extend(comments);
lines.push("".to_owned());
}
pub fn gen_code_blocks(entity: &Entity) -> Vec<TokenStream> {

View File

@ -3,7 +3,6 @@ use std::{error, fmt, io};
#[derive(Debug)]
pub enum Error {
StdIoError(io::Error),
SqlxError(sqlx::Error),
TransformError(String),
}
@ -11,7 +10,6 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::StdIoError(e) => write!(f, "{:?}", e),
Self::SqlxError(e) => write!(f, "{:?}", e),
Self::TransformError(e) => write!(f, "{:?}", e),
}
}
@ -21,7 +19,6 @@ impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::StdIoError(e) => Some(e),
Self::SqlxError(e) => Some(e),
Self::TransformError(_) => None,
}
}
@ -32,9 +29,3 @@ impl From<io::Error> for Error {
Self::StdIoError(io_err)
}
}
impl From<sqlx::Error> for Error {
fn from(sqlx_err: sqlx::Error) -> Self {
Self::SqlxError(sqlx_err)
}
}