Skip to content

Commit

Permalink
Add actix framework generator option (#74)
Browse files Browse the repository at this point in the history
* Add actix framework generator option
  • Loading branch information
karatakis authored Nov 7, 2022
1 parent bb6d85f commit 04c3034
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 116 deletions.
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

0 comments on commit 04c3034

Please sign in to comment.