Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add actix framework generator option #74

Merged
merged 2 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
with:
command: clippy
args: "-- -D warnings"
integration-sqlite:
integration-sqlite-poem:
name: SQLite integration tests
runs-on: ubuntu-latest
needs:
Expand All @@ -94,7 +94,36 @@ jobs:
command: run
args: >
--package seaography-cli --
sqlite://sakila.db seaography-sqlite-example ./examples/sqlite
-f poem sqlite://sakila.db seaography-sqlite-example ./examples/sqlite
- name: Depends on local seaography
run: sed -i '/^\[dependencies.seaography\]$/a \path = "..\/..\/"' ./examples/sqlite/Cargo.toml
- name: Integration tests
working-directory: ./examples/sqlite
run: cargo test

integration-sqlite-actix:
name: SQLite integration tests
runs-on: ubuntu-latest
needs:
- check
- test
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Remove generated folder
run: rm -rf ./examples/sqlite/src
- name: Copy sample database
run: cp ./examples/sqlite/sakila.db .
- uses: actions-rs/cargo@v1
with:
command: run
args: >
--package seaography-cli --
-f actix sqlite://sakila.db seaography-sqlite-example ./examples/sqlite
- name: Depends on local seaography
run: sed -i '/^\[dependencies.seaography\]$/a \path = "..\/..\/"' ./examples/sqlite/Cargo.toml
- name: Integration tests
Expand Down
6 changes: 5 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use clap::Parser;
use seaography_generator::write_project;
use seaography_generator::{write_project, WebFrameworkEnum};

#[derive(clap::Parser)]
#[clap(author, version, about, long_about = None)]
Expand Down Expand Up @@ -27,6 +27,9 @@ pub struct Args {

#[clap(short, long)]
pub hidden_tables: Option<bool>,

#[clap(short, long)]
pub framework: Option<WebFrameworkEnum>,
}

/**
Expand Down Expand Up @@ -140,6 +143,7 @@ async fn main() {
expanded_format,
tables,
sql_library,
args.framework.unwrap_or_else(|| WebFrameworkEnum::Poem),
args.depth_limit,
args.complexity_limit,
)
Expand Down
2 changes: 1 addition & 1 deletion examples/sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ name = 'seaography-sqlite-example'
version = '0.2.0'

[dependencies]
poem = { version = "1.3.29" }
async-graphql = { version = "4.0.14", features = ["decimal", "chrono", "dataloader"] }
async-graphql-poem = { version = "4.0.14" }
async-trait = { version = "0.1.53" }
dotenv = "0.15.0"
poem = { version = "1.3.29" }
sea-orm = { version = "^0.9", features = ["sqlx-sqlite", "runtime-async-std-native-tls"] }
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.34" }
Expand Down
46 changes: 29 additions & 17 deletions examples/sqlite/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use actix_web::{guard, web, web::Data, App, HttpResponse, HttpServer, Result};
use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
use dotenv::dotenv;
use lazy_static::lazy_static;
use poem::{get, handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_sqlite_example::*;
use std::env;
Expand All @@ -25,19 +25,27 @@ lazy_static! {
});
}

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new(&*ENDPOINT)))
type AppSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;

async fn index(schema: web::Data<AppSchema>, req: GraphQLRequest) -> GraphQLResponse {
schema.execute(req.into_inner()).await.into()
}

async fn graphql_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(playground_source(GraphQLPlaygroundConfig::new(
"http://localhost:8000",
))))
}

#[tokio::main]
async fn main() {
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_test_writer()
.init();

let database = Database::connect(&*DATABASE_URL)
.await
.expect("Fail to initialize database connection");
Expand All @@ -57,14 +65,18 @@ async fn main() {
schema = schema.limit_complexity(complexity);
}
let schema = schema.finish();
let app = Route::new().at(
&*ENDPOINT,
get(graphql_playground).post(GraphQL::new(schema)),
);

println!("Visit GraphQL Playground at http://{}", *URL);
Server::new(TcpListener::bind(&*URL))
.run(app)
.await
.expect("Fail to start web server");
HttpServer::new(move || {
App::new()
.app_data(Data::new(schema.clone()))
.service(web::resource("/").guard(guard::Post()).to(index))
.service(
web::resource("/")
.guard(guard::Get())
.to(graphql_playground),
)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
43 changes: 41 additions & 2 deletions generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,55 @@ pub mod error;
pub use error::{Error, Result};
pub mod inject_graphql;
pub mod sea_orm_codegen;
pub mod templates;
pub mod writer;

mod util;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum WebFrameworkEnum {
Actix,
Poem,
}

impl std::str::FromStr for WebFrameworkEnum {
type Err = String;

fn from_str(input: &str) -> std::result::Result<WebFrameworkEnum, Self::Err> {
match input {
"actix" => Ok(Self::Actix),
"poem" => Ok(Self::Poem),
_ => Err(format!(
"Invalid framework '{}', 'actix' and 'poem' are supported!",
input
)),
}
}
}

impl std::fmt::Display for WebFrameworkEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WebFrameworkEnum::Actix => f.write_str("actix"),
WebFrameworkEnum::Poem => f.write_str("poem"),
}
}
}

pub async fn write_project<P: AsRef<Path>>(
path: &P,
db_url: &str,
crate_name: &str,
expanded_format: bool,
tables: std::collections::BTreeMap<String, sea_query::TableCreateStatement>,
sql_library: &str,
framework: WebFrameworkEnum,
depth_limit: Option<usize>,
complexity_limit: Option<usize>,
) -> Result<()> {
std::fs::create_dir_all(&path.as_ref().join("src/entities"))?;

writer::write_cargo_toml(path, crate_name, &sql_library)?;
writer::write_cargo_toml(path, crate_name, &sql_library, framework)?;

let src_path = &path.as_ref().join("src");

Expand All @@ -32,7 +64,14 @@ pub async fn write_project<P: AsRef<Path>>(

writer::write_query_root(src_path, &entities_hashmap).unwrap();
writer::write_lib(src_path)?;
writer::write_main(src_path, crate_name)?;

match framework {
WebFrameworkEnum::Actix => {
crate::templates::actix::write_main(src_path, crate_name).unwrap()
}
WebFrameworkEnum::Poem => crate::templates::poem::write_main(src_path, crate_name).unwrap(),
}

writer::write_env(&path.as_ref(), db_url, depth_limit, complexity_limit)?;

sea_orm_codegen::write_entities(&src_path.join("entities"), entities_hashmap).unwrap();
Expand Down
103 changes: 103 additions & 0 deletions generator/src/templates/actix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use proc_macro2::TokenStream;
use quote::quote;

use crate::util::add_line_break;

///
/// Used to generate project/src/main.rs file content
///
pub fn generate_main(crate_name: &str) -> TokenStream {
let crate_name_token: TokenStream = crate_name.replace('-', "_").parse().unwrap();

quote! {
use actix_web::{guard, web, web::Data, App, HttpResponse, HttpServer, Result};
use async_graphql::{
dataloader::DataLoader, http::{playground_source, GraphQLPlaygroundConfig}, EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
use dotenv::dotenv;
use lazy_static::lazy_static;
use sea_orm::Database;
use #crate_name_token::*;
use std::env;

lazy_static! {
static ref URL: String = env::var("URL").unwrap_or("0.0.0.0:8000".into());
static ref ENDPOINT: String = env::var("ENDPOINT").unwrap_or("/".into());
static ref DATABASE_URL: String =
env::var("DATABASE_URL").expect("DATABASE_URL environment variable not set");
static ref DEPTH_LIMIT: Option<usize> = env::var("DEPTH_LIMIT").map_or(None, |data| Some(
data.parse().expect("DEPTH_LIMIT is not a number")
));
static ref COMPLEXITY_LIMIT: Option<usize> = env::var("COMPLEXITY_LIMIT")
.map_or(None, |data| {
Some(data.parse().expect("COMPLEXITY_LIMIT is not a number"))
});
}

type AppSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;

async fn index(schema: web::Data<AppSchema>, req: GraphQLRequest) -> GraphQLResponse {
schema.execute(req.into_inner()).await.into()
}

async fn graphql_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(
playground_source(GraphQLPlaygroundConfig::new("http://localhost:8000"))
))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_test_writer()
.init();

let database = Database::connect(&*DATABASE_URL)
.await
.expect("Fail to initialize database connection");
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader {
db: database.clone(),
},
tokio::spawn,
);
let mut schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader);
if let Some(depth) = *DEPTH_LIMIT {
schema = schema.limit_depth(depth);
}
if let Some(complexity) = *COMPLEXITY_LIMIT {
schema = schema.limit_complexity(complexity);
}
let schema = schema.finish();

println!("Visit GraphQL Playground at http://{}", *URL);

HttpServer::new(move || {
App::new()
.app_data(Data::new(schema.clone()))
.service(web::resource("/").guard(guard::Post()).to(index))
.service(web::resource("/").guard(guard::Get()).to(graphql_playground))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
}
}

pub fn write_main<P: AsRef<std::path::Path>>(path: &P, crate_name: &str) -> std::io::Result<()> {
let tokens = generate_main(crate_name);

let file_name = path.as_ref().join("main.rs");

std::fs::write(file_name, add_line_break(tokens))?;

Ok(())
}
26 changes: 26 additions & 0 deletions generator/src/templates/actix_cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
edition = '2021'
name = '<seaography-package-name>'
version = '0.1.0'

[dependencies]
actix-web = { version = "4.0.1", default-features = false, features = ["macros"] }
async-graphql = { version = "4.0.14", features = ["decimal", "chrono", "dataloader"] }
async-graphql-actix-web = { version = "4.0.14" }
async-trait = { version = "0.1.53" }
dotenv = "0.15.0"
sea-orm = { version = "^0.9", features = ["<seaography-sql-library>", "runtime-async-std-native-tls"] }
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.34" }
tracing-subscriber = { version = "0.3.11" }
lazy_static = { version = "1.4.0" }

[dependencies.seaography]
version = "<seaography-version>" # seaography version
features = ["with-decimal", "with-chrono"]

[dev-dependencies]
serde_json = { version = '1.0.82' }

[workspace]
members = []
2 changes: 2 additions & 0 deletions generator/src/templates/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod actix;
pub mod poem;
Loading