Skip to content

Commit 569ea57

Browse files
authored
Merge pull request #695 from dkackman/swagger
Swagger
2 parents dc135de + 6b544b2 commit 569ea57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4690
-271
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ dist-ssr
2525

2626
# Rust
2727
target
28-
/.env
28+
.env
2929
/test.sqlite*
30+

Cargo.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ specta = "2.0.0-rc.20"
8484
specta-typescript = "0.0.7"
8585
tauri-specta = "2.0.0-rc.20"
8686

87+
# OpenAPI
88+
utoipa = "5.2.0"
89+
8790
# Chia
8891
chia = "0.26.0"
8992
chia_streamable_macro = "0.26.0"
@@ -123,6 +126,7 @@ expect-test = "1.5.1"
123126
convert_case = "0.8.0"
124127
quote = "1.0.38"
125128
proc-macro2 = "1.0.93"
129+
syn = "2.0"
126130

127131
# Tracing
128132
tracing = "0.1.40"

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,5 @@ The following things have to be done before a new release is published:
116116
4. Run `pnpm extract` to extract new translations
117117
5. Create a new tagged release
118118
6. Upload to TestFlight and the Google Play Store
119+
7. Generate the OpenAPI specification `cargo run --bin sage rpc generate_openapi -o dist/openapi.json`
120+
8. Upload to docusaurus

crates/sage-api/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ workspace = true
1616

1717
[features]
1818
tauri = ["dep:tauri-specta", "dep:specta"]
19+
openapi = ["dep:utoipa", "dep:sage-api-macro"]
1920

2021
[dependencies]
2122
sage-config = { workspace = true }
23+
sage-api-macro = { workspace = true, optional = true }
2224
serde = { workspace = true, features = ["derive"] }
2325
tauri-specta = { workspace = true, features = ["derive"], optional = true }
2426
specta = { workspace = true, features = ["derive", "bigdecimal"], optional = true }
27+
utoipa = { workspace = true, optional = true }

crates/sage-api/macro/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ convert_case = { workspace = true }
2323
serde_json = { workspace = true }
2424
indexmap = { workspace = true, features = ["serde"] }
2525
proc-macro2 = { workspace = true }
26+
syn = { workspace = true, features = ["full"] }

crates/sage-api/macro/src/lib.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
mod openapi;
2+
13
use convert_case::{Case, Casing};
24
use indexmap::IndexMap;
35
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
4-
use proc_macro2::Ident;
56
use quote::quote;
7+
use syn::parse_macro_input;
68

79
#[proc_macro]
810
pub fn impl_endpoints(input: TokenStream) -> TokenStream {
@@ -14,6 +16,58 @@ pub fn impl_endpoints_tauri(input: TokenStream) -> TokenStream {
1416
generate(&input, true)
1517
}
1618

19+
/// Attribute macro for `OpenAPI` metadata
20+
///
21+
/// Usage:
22+
/// ```
23+
/// #[openapi(
24+
/// tag = "Authentication & Keys",
25+
/// description = "Authenticate and log into a wallet..."
26+
/// )]
27+
/// pub struct Login { ... }
28+
/// ```
29+
#[proc_macro_attribute]
30+
pub fn openapi(args: TokenStream, input: TokenStream) -> TokenStream {
31+
let args = parse_macro_input!(args as openapi::OpenApiArgs);
32+
let input = parse_macro_input!(input as syn::DeriveInput);
33+
34+
openapi::impl_openapi_metadata(&args, &input).into()
35+
}
36+
37+
/// Generates schema registrations from endpoints.json
38+
///
39+
/// Automatically reads endpoints.json and generates `.schema_from::<Type>()`
40+
/// calls for all endpoint request/response types.
41+
#[proc_macro]
42+
pub fn register_openapi_types(input: TokenStream) -> TokenStream {
43+
openapi::impl_openapi_registration(input)
44+
}
45+
46+
/// Generates endpoint metadata match arms from endpoints.json
47+
///
48+
/// Automatically generates match arms that retrieve tag and description
49+
/// from `OpenApiMetadata` trait implementations.
50+
#[proc_macro]
51+
pub fn endpoint_metadata(input: TokenStream) -> TokenStream {
52+
openapi::impl_endpoint_metadata(input)
53+
}
54+
55+
/// Generates request schema match arms from endpoints.json
56+
///
57+
/// Automatically generates match arms mapping endpoints to their request schemas.
58+
#[proc_macro]
59+
pub fn request_schemas(input: TokenStream) -> TokenStream {
60+
openapi::impl_request_schemas(input)
61+
}
62+
63+
/// Generates response schema match arms from endpoints.json
64+
///
65+
/// Automatically generates match arms mapping endpoints to their response schemas.
66+
#[proc_macro]
67+
pub fn response_schemas(input: TokenStream) -> TokenStream {
68+
openapi::impl_response_schemas(input)
69+
}
70+
1771
fn generate(input: &TokenStream, tauri: bool) -> TokenStream {
1872
let mut endpoints: IndexMap<String, bool> =
1973
serde_json::from_str(include_str!("../../endpoints.json")).expect("Invalid endpoint file");
@@ -62,13 +116,13 @@ fn convert(
62116
output.extend(quote!(.await));
63117
}
64118
} else if ident.is_case(Case::Snake) {
65-
let ident = Ident::new(
119+
let ident = proc_macro2::Ident::new(
66120
&ident.replace("endpoint", &endpoint.to_case(Case::Snake)),
67121
old.span().into(),
68122
);
69123
output.extend(quote!(#ident));
70124
} else if ident.is_case(Case::Pascal) {
71-
let ident = Ident::new(
125+
let ident = proc_macro2::Ident::new(
72126
&ident.replace("Endpoint", &endpoint.to_case(Case::Pascal)),
73127
old.span().into(),
74128
);

0 commit comments

Comments
 (0)