Skip to content

Commit 5bc366c

Browse files
committed
feat: draft of extracting service integrations
1 parent 4e1690d commit 5bc366c

File tree

6 files changed

+160
-43
lines changed

6 files changed

+160
-43
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ exclude = [
2020
"resources/persist",
2121
"resources/secrets",
2222
"resources/shared-db",
23-
"resources/static-folder"
23+
"resources/static-folder",
24+
"integrations"
2425
]
2526

2627
[workspace.package]

codegen/src/shuttle_main/mod.rs

Lines changed: 95 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,18 @@ use quote::{quote, ToTokens};
44
use syn::{
55
parenthesized, parse::Parse, parse2, parse_macro_input, parse_quote, punctuated::Punctuated,
66
spanned::Spanned, token::Paren, Attribute, Expr, FnArg, Ident, ItemFn, Pat, PatIdent, Path,
7-
ReturnType, Signature, Stmt, Token, Type, TypePath,
7+
PathSegment, ReturnType, Signature, Stmt, Token, Type, TypePath,
88
};
99

1010
pub(crate) fn r#impl(_attr: TokenStream, item: TokenStream) -> TokenStream {
1111
let mut fn_decl = parse_macro_input!(item as ItemFn);
1212

1313
let loader = Loader::from_item_fn(&mut fn_decl);
1414

15+
let main_fn = MainFn::from_item_fn(&mut fn_decl);
16+
1517
let expanded = quote! {
16-
#[tokio::main]
17-
async fn main() {
18-
shuttle_runtime::start(loader).await;
19-
}
18+
#main_fn
2019

2120
#loader
2221

@@ -30,6 +29,11 @@ struct Loader {
3029
fn_ident: Ident,
3130
fn_inputs: Vec<Input>,
3231
fn_return: TypePath,
32+
import_path: PathSegment,
33+
}
34+
35+
struct MainFn {
36+
import_path: PathSegment,
3337
}
3438

3539
#[derive(Debug, PartialEq)]
@@ -128,17 +132,38 @@ impl Loader {
128132
.collect();
129133

130134
if let Some(type_path) = check_return_type(item_fn.sig.clone()) {
135+
// We need the first segment of the path so we can import the codegen dependencies from it.
136+
let Some(import_path) = type_path.path.segments.first().cloned() else {
137+
return None;
138+
};
139+
131140
Some(Self {
132141
fn_ident: item_fn.sig.ident.clone(),
133142
fn_inputs: inputs,
134143
fn_return: type_path,
144+
import_path,
135145
})
136146
} else {
137147
None
138148
}
139149
}
140150
}
141151

152+
impl MainFn {
153+
pub(crate) fn from_item_fn(item_fn: &mut ItemFn) -> Option<Self> {
154+
if let Some(type_path) = check_return_type(item_fn.sig.clone()) {
155+
// We need the first segment of the path so we can import the codegen dependencies from it.
156+
let Some(import_path) = type_path.path.segments.first().cloned() else {
157+
return None;
158+
};
159+
160+
Some(Self { import_path })
161+
} else {
162+
None
163+
}
164+
}
165+
}
166+
142167
fn check_return_type(signature: Signature) -> Option<TypePath> {
143168
match signature.output {
144169
ReturnType::Default => {
@@ -193,6 +218,8 @@ impl ToTokens for Loader {
193218

194219
let return_type = &self.fn_return;
195220

221+
let import_path = &self.import_path;
222+
196223
let mut fn_inputs: Vec<_> = Vec::with_capacity(self.fn_inputs.len());
197224
let mut fn_inputs_builder: Vec<_> = Vec::with_capacity(self.fn_inputs.len());
198225
let mut fn_inputs_builder_options: Vec<_> = Vec::with_capacity(self.fn_inputs.len());
@@ -213,25 +240,26 @@ impl ToTokens for Loader {
213240
None
214241
} else {
215242
Some(parse_quote!(
216-
use shuttle_service::ResourceBuilder;
243+
use #import_path::shuttle_runtime::ResourceBuilder;
217244
))
218245
};
219246

247+
// let import_lib: TypePath = parse_quote!( shuttle_<#fn_ident>);
220248
let loader = quote! {
221-
async fn loader<S: shuttle_runtime::StorageManager>(
222-
mut #factory_ident: shuttle_runtime::ProvisionerFactory<S>,
223-
logger: shuttle_runtime::Logger,
249+
async fn loader<S: #import_path::shuttle_runtime::StorageManager>(
250+
mut #factory_ident: #import_path::shuttle_runtime::ProvisionerFactory<S>,
251+
logger: #import_path::shuttle_runtime::Logger,
224252
) -> #return_type {
225-
use shuttle_service::Context;
226-
use shuttle_service::tracing_subscriber::prelude::*;
253+
use #import_path::shuttle_runtime::Context;
254+
use #import_path::shuttle_runtime::tracing_subscriber::prelude::*;
227255
#extra_imports
228256

229257
let filter_layer =
230-
shuttle_service::tracing_subscriber::EnvFilter::try_from_default_env()
231-
.or_else(|_| shuttle_service::tracing_subscriber::EnvFilter::try_new("INFO"))
258+
#import_path::shuttle_runtime::tracing_subscriber::EnvFilter::try_from_default_env()
259+
.or_else(|_| #import_path::shuttle_runtime::tracing_subscriber::EnvFilter::try_new("INFO"))
232260
.unwrap();
233261

234-
shuttle_service::tracing_subscriber::registry()
262+
#import_path::shuttle_runtime::tracing_subscriber::registry()
235263
.with(filter_layer)
236264
.with(logger)
237265
.init();
@@ -246,11 +274,28 @@ impl ToTokens for Loader {
246274
}
247275
}
248276

277+
impl ToTokens for MainFn {
278+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
279+
let import_path = &self.import_path;
280+
281+
// let import_lib: TypePath = parse_quote!( shuttle_<#fn_ident>);
282+
let main_fn = quote! {
283+
#[tokio::main]
284+
async fn main() {
285+
#import_path::shuttle_runtime::start(loader).await;
286+
}
287+
288+
};
289+
290+
main_fn.to_tokens(tokens);
291+
}
292+
}
293+
249294
#[cfg(test)]
250295
mod tests {
251296
use pretty_assertions::assert_eq;
252297
use quote::quote;
253-
use syn::{parse_quote, Ident};
298+
use syn::{parse_quote, Ident, PathSegment};
254299

255300
use super::{Builder, BuilderOptions, Input, Loader};
256301

@@ -269,27 +314,30 @@ mod tests {
269314

270315
#[test]
271316
fn output_with_return() {
317+
let import_path: PathSegment = parse_quote!(shuttle_simple);
318+
272319
let input = Loader {
273320
fn_ident: parse_quote!(simple),
274321
fn_inputs: Vec::new(),
275322
fn_return: parse_quote!(ShuttleSimple),
323+
import_path,
276324
};
277325

278326
let actual = quote!(#input);
279327
let expected = quote! {
280-
async fn loader<S: shuttle_runtime::StorageManager>(
281-
mut _factory: shuttle_runtime::ProvisionerFactory<S>,
282-
logger: shuttle_runtime::Logger,
328+
async fn loader<S: shuttle_simple::shuttle_runtime::StorageManager>(
329+
mut _factory: shuttle_simple::shuttle_runtime::ProvisionerFactory<S>,
330+
logger: shuttle_simple::shuttle_runtime::Logger,
283331
) -> ShuttleSimple {
284-
use shuttle_service::Context;
285-
use shuttle_service::tracing_subscriber::prelude::*;
332+
use shuttle_simple::shuttle_runtime::Context;
333+
use shuttle_simple::shuttle_runtime::tracing_subscriber::prelude::*;
286334

287335
let filter_layer =
288-
shuttle_service::tracing_subscriber::EnvFilter::try_from_default_env()
289-
.or_else(|_| shuttle_service::tracing_subscriber::EnvFilter::try_new("INFO"))
336+
shuttle_simple::shuttle_runtime::tracing_subscriber::EnvFilter::try_from_default_env()
337+
.or_else(|_| shuttle_simple::shuttle_runtime::tracing_subscriber::EnvFilter::try_new("INFO"))
290338
.unwrap();
291339

292-
shuttle_service::tracing_subscriber::registry()
340+
shuttle_simple::shuttle_runtime::tracing_subscriber::registry()
293341
.with(filter_layer)
294342
.with(logger)
295343
.init();
@@ -334,6 +382,8 @@ mod tests {
334382

335383
#[test]
336384
fn output_with_inputs() {
385+
let import_path: PathSegment = parse_quote!(shuttle_complex);
386+
337387
let input = Loader {
338388
fn_ident: parse_quote!(complex),
339389
fn_inputs: vec![
@@ -353,24 +403,25 @@ mod tests {
353403
},
354404
],
355405
fn_return: parse_quote!(ShuttleComplex),
406+
import_path,
356407
};
357408

358409
let actual = quote!(#input);
359410
let expected = quote! {
360-
async fn loader<S: shuttle_runtime::StorageManager>(
361-
mut factory: shuttle_runtime::ProvisionerFactory<S>,
362-
logger: shuttle_runtime::Logger,
411+
async fn loader<S: shuttle_complex::shuttle_runtime::StorageManager>(
412+
mut factory: shuttle_complex::shuttle_runtime::ProvisionerFactory<S>,
413+
logger: shuttle_complex::shuttle_runtime::Logger,
363414
) -> ShuttleComplex {
364-
use shuttle_service::Context;
365-
use shuttle_service::tracing_subscriber::prelude::*;
366-
use shuttle_service::ResourceBuilder;
415+
use shuttle_complex::shuttle_runtime::Context;
416+
use shuttle_complex::shuttle_runtime::tracing_subscriber::prelude::*;
417+
use shuttle_complex::shuttle_runtime::ResourceBuilder;
367418

368419
let filter_layer =
369-
shuttle_service::tracing_subscriber::EnvFilter::try_from_default_env()
370-
.or_else(|_| shuttle_service::tracing_subscriber::EnvFilter::try_new("INFO"))
420+
shuttle_complex::shuttle_runtime::tracing_subscriber::EnvFilter::try_from_default_env()
421+
.or_else(|_| shuttle_complex::shuttle_runtime::tracing_subscriber::EnvFilter::try_new("INFO"))
371422
.unwrap();
372423

373-
shuttle_service::tracing_subscriber::registry()
424+
shuttle_complex::shuttle_runtime::tracing_subscriber::registry()
374425
.with(filter_layer)
375426
.with(logger)
376427
.init();
@@ -460,6 +511,8 @@ mod tests {
460511

461512
#[test]
462513
fn output_with_input_options() {
514+
let import_path: PathSegment = parse_quote!(shuttle_complex);
515+
463516
let mut input = Loader {
464517
fn_ident: parse_quote!(complex),
465518
fn_inputs: vec![Input {
@@ -470,6 +523,7 @@ mod tests {
470523
},
471524
}],
472525
fn_return: parse_quote!(ShuttleComplex),
526+
import_path,
473527
};
474528

475529
input.fn_inputs[0]
@@ -485,20 +539,20 @@ mod tests {
485539

486540
let actual = quote!(#input);
487541
let expected = quote! {
488-
async fn loader<S: shuttle_runtime::StorageManager>(
489-
mut factory: shuttle_runtime::ProvisionerFactory<S>,
490-
logger: shuttle_runtime::Logger,
542+
async fn loader<S: shuttle_complex::shuttle_runtime::StorageManager>(
543+
mut factory: shuttle_complex::shuttle_runtime::ProvisionerFactory<S>,
544+
logger: shuttle_complex::shuttle_runtime::Logger,
491545
) -> ShuttleComplex {
492-
use shuttle_service::Context;
493-
use shuttle_service::tracing_subscriber::prelude::*;
494-
use shuttle_service::ResourceBuilder;
546+
use shuttle_complex::shuttle_runtime::Context;
547+
use shuttle_complex::shuttle_runtime::tracing_subscriber::prelude::*;
548+
use shuttle_complex::shuttle_runtime::ResourceBuilder;
495549

496550
let filter_layer =
497-
shuttle_service::tracing_subscriber::EnvFilter::try_from_default_env()
498-
.or_else(|_| shuttle_service::tracing_subscriber::EnvFilter::try_new("INFO"))
551+
shuttle_complex::shuttle_runtime::tracing_subscriber::EnvFilter::try_from_default_env()
552+
.or_else(|_| shuttle_complex::shuttle_runtime::tracing_subscriber::EnvFilter::try_new("INFO"))
499553
.unwrap();
500554

501-
shuttle_service::tracing_subscriber::registry()
555+
shuttle_complex::shuttle_runtime::tracing_subscriber::registry()
502556
.with(filter_layer)
503557
.with(logger)
504558
.init();

integrations/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Service Integrations
2+
The list of supported frameworks for shuttle is always growing. If you feel we are missing a framework you would like, then feel to create a feature request for your desired framework.
3+
4+
## Writing your own service integration
5+
Creating your own service integration is quite simple. You only need to implement the [`Service`](https://docs.rs/shuttle-service/latest/shuttle_service/trait.Service.html) trait for your framework.

integrations/shuttle-axum/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "shuttle-axum"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[workspace]
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
async-trait = "0.1.56"
11+
axum = { version = "0.6.0" }
12+
shuttle-runtime = { path = "../../runtime", version = "0.1.0" }

integrations/shuttle-axum/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Shuttle service integration for the Axum web framework.
2+
//! ## Example
3+
//! ```rust,no_run
4+
//! use shuttle_axum::AxumService;
5+
//!
6+
//! async fn hello_world() -> &'static str {
7+
//! "Hello, world!"
8+
//! }
9+
//!
10+
//! #[shuttle_axum::main]
11+
//! async fn axum() -> shuttle_service::ShuttleAxum {
12+
//! let router = Router::new().route("/hello", get(hello_world));
13+
//!
14+
//! Ok(AxumService(router))
15+
//! }
16+
//! ```
17+
18+
/// A wrapper type for `axum::Router` so we can implement `shuttle_runtime::Service` for it.
19+
pub struct AxumService(pub axum::Router);
20+
21+
#[shuttle_runtime::async_trait]
22+
impl shuttle_runtime::Service for AxumService {
23+
/// Takes the router that is returned by the user in their `shuttle_runtime::main` function
24+
/// and binds to an address passed in by shuttle.
25+
async fn bind(mut self, addr: std::net::SocketAddr) -> Result<(), shuttle_runtime::Error> {
26+
axum::Server::bind(&addr)
27+
.serve(self.0.into_make_service())
28+
.await
29+
.map_err(shuttle_runtime::CustomError::new)?;
30+
31+
Ok(())
32+
}
33+
}
34+
35+
/// The return type that should be returned from the `shuttle_runtime::main` function.
36+
pub type ShuttleAxum = Result<AxumService, shuttle_runtime::Error>;
37+
38+
pub use shuttle_runtime;
39+
pub use shuttle_runtime::*;

runtime/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ mod logger;
44
mod next;
55
mod provisioner_factory;
66

7+
pub use async_trait::async_trait;
78
pub use legacy::{start, Legacy};
89
pub use logger::Logger;
910
#[cfg(feature = "next")]
1011
pub use next::{AxumWasm, NextArgs};
1112
pub use provisioner_factory::ProvisionerFactory;
1213
pub use shuttle_common::storage_manager::StorageManager;
13-
pub use shuttle_service::{main, Error, Service};
14+
pub use shuttle_service::{main, Error, ResourceBuilder, Service};
15+
16+
pub type CustomError = anyhow::Error;
17+
pub use anyhow::Context;
18+
pub use tracing;
19+
pub use tracing_subscriber;

0 commit comments

Comments
 (0)