Skip to content

Commit c333982

Browse files
authored
Merge pull request #100 from dev-five-git/impl-jni
Impl jni
2 parents c5b59b8 + c3187bd commit c333982

43 files changed

Lines changed: 4121 additions & 146 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"libs/vespera-bridge/build.gradle.kts":"Minor","Cargo.toml":"Patch"},"note":"Initial","date":"2026-03-21T07:48:00.475937500Z"}

.changepacks/config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"ignore": ["**", "!crates/**"],
2+
"ignore": ["**", "!crates/**", "!libs/**"],
33
"baseBranch": "main",
44
"latestPackage": "crates/vespera/Cargo.toml",
55
"publish": {}

.github/workflows/CI.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,29 @@ jobs:
8080
publish: true
8181
env:
8282
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
83+
84+
# publish vespera-bridge Java library to GitHub Packages
85+
publish-java:
86+
name: Publish vespera-bridge
87+
runs-on: ubuntu-latest
88+
needs:
89+
- changepacks
90+
if: contains(needs.changepacks.outputs.changepacks, 'libs/vespera-bridge')
91+
permissions:
92+
packages: write
93+
steps:
94+
- uses: actions/checkout@v5
95+
96+
- uses: actions/setup-java@v4
97+
with:
98+
distribution: 'temurin'
99+
java-version: '17'
100+
101+
- uses: gradle/actions/setup-gradle@v4
102+
103+
- name: Publish to GitHub Packages
104+
working-directory: libs/vespera-bridge
105+
run: ./gradlew publish
106+
env:
107+
GITHUB_ACTOR: ${{ github.actor }}
108+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,16 @@ build_rs_cov.profraw
77
.claude
88
.DS_Store
99
coverage-report
10+
11+
# Java build artifacts
12+
*.class
13+
.gradle/
14+
build/
15+
bin/
16+
17+
# IDE
18+
.settings/
19+
.classpath
20+
.project
21+
*.iml
22+
.idea/

AGENTS.md

Lines changed: 129 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
# VESPERA PROJECT KNOWLEDGE BASE
22

3-
**Generated:** 2026-02-04
3+
**Generated:** 2026-03-21
44
**Branch:** main
55

66
## OVERVIEW
77

88
Vespera is a fully automated OpenAPI 3.1 engine for Axum - delivers FastAPI-like DX to Rust. Zero-config route discovery via compile-time macro scanning.
99

10+
Also provides in-process dispatch (`vespera_inprocess` crate) and JNI integration (`vespera_jni` crate) for embedding Rust axum apps inside Java/Spring applications without HTTP overhead.
11+
1012
## STRUCTURE
1113

1214
```
1315
vespera/
1416
├── crates/
15-
│ ├── vespera/ # Public API - re-exports everything (+ chrono re-export)
16-
│ ├── vespera_core/ # OpenAPI types, route/schema abstractions
17-
│ └── vespera_macro/ # Proc-macros (main logic lives here)
18-
└── examples/axum-example/ # Demo app with route patterns
17+
│ ├── vespera/ # Public API - re-exports everything
18+
│ │ └── src/lib.rs # Core re-exports (no transport deps)
19+
│ ├── vespera_core/ # OpenAPI types, route/schema abstractions
20+
│ ├── vespera_macro/ # Proc-macros (main logic lives here)
21+
│ ├── vespera_inprocess/ # In-process dispatch (transport-agnostic)
22+
│ │ └── src/lib.rs # dispatch(), register_app(), dispatch_from_json()
23+
│ └── vespera_jni/ # JNI bridge (depends on vespera_inprocess)
24+
│ └── src/lib.rs # RUNTIME, jni_app! macro, JNI symbol export
25+
├── libs/
26+
│ └── vespera-bridge/ # Java library (com.devfive.vespera.bridge)
27+
│ ├── VesperaBridge.java # JNI native loader + dispatch
28+
│ └── VesperaProxyController.java # Auto-configured Spring proxy
29+
├── examples/
30+
│ ├── axum-example/ # Standard axum server demo
31+
│ └── rust-jni-demo/ # JNI + standalone server demo
32+
│ ├── src/ # Rust: routes, create_app(), jni_app!
33+
│ └── java/demo-app/ # Java: Spring Boot proxy
1934
```
2035

2136
## WHERE TO LOOK
@@ -30,6 +45,12 @@ vespera/
3045
| Modify schema_type! macro | `crates/vespera_macro/src/schema_macro.rs` | Type derivation & SeaORM support |
3146
| Add core types | `crates/vespera_core/src/` | OpenAPI spec types |
3247
| Test new features | `examples/axum-example/` | Add route, run example |
48+
| In-process dispatch | `crates/vespera_inprocess/src/lib.rs` | RequestEnvelope → Router → ResponseEnvelope |
49+
| App factory (FFI pattern) | `crates/vespera_inprocess/src/lib.rs` | register_app(), dispatch_from_json() |
50+
| JNI integration | `crates/vespera_jni/src/lib.rs` | RUNTIME, jni_app! macro, JNI symbol export |
51+
| Java bridge library | `libs/vespera-bridge/` | com.devfive.vespera.bridge package |
52+
| JNI demo (Rust) | `examples/rust-jni-demo/src/` | Routes + vespera::jni_app! |
53+
| JNI demo (Java) | `examples/rust-jni-demo/java/` | Spring Boot proxy app |
3354

3455
## KEY COMPONENTS
3556

@@ -41,13 +62,93 @@ vespera/
4162
| `vespera_macro/src/parser/parameters.rs` | ~845 | Extract path/query params from handlers |
4263
| `vespera_macro/src/openapi_generator.rs` | ~808 | OpenAPI doc assembly |
4364
| `vespera_macro/src/collector.rs` | ~707 | Filesystem route scanning |
65+
| `vespera_inprocess/src/lib.rs` | ~175 | In-process dispatch + app factory |
66+
| `vespera_jni/src/lib.rs` | ~95 | JNI RUNTIME + jni_app! macro + JNI symbol |
67+
68+
## CRATE DEPENDENCY GRAPH
69+
70+
```
71+
vespera (OpenAPI framework)
72+
├── vespera_core
73+
├── vespera_macro
74+
├── vespera_inprocess (optional, feature = "inprocess")
75+
└── vespera_jni (optional, feature = "jni", implies "inprocess")
76+
77+
vespera_inprocess (transport layer — no JNI deps)
78+
├── axum (direct — owns Router re-export)
79+
├── http, http-body-util, tower
80+
├── serde, serde_json
81+
└── tokio (rt only — for dispatch_from_json Runtime param)
82+
83+
vespera_jni (JNI glue — thin layer)
84+
├── vespera_inprocess (via workspace)
85+
├── jni
86+
└── tokio (rt-multi-thread — for LazyLock<Runtime>)
87+
88+
rust-jni-demo (example — depends on vespera ONLY)
89+
└── vespera = { features = ["jni"] }
90+
```
91+
92+
## USER-FACING API
93+
94+
Users depend on `vespera` only. Internal crates are never depended on directly.
95+
96+
```toml
97+
# Cargo.toml — the only dependency needed
98+
[dependencies]
99+
vespera = { version = "...", features = ["jni"] }
100+
```
101+
102+
```rust
103+
// lib.rs — all imports come from vespera
104+
use vespera::{axum, vespera};
105+
106+
pub fn create_app() -> axum::Router {
107+
vespera!(title = "My API", version = "1.0.0")
108+
}
109+
110+
vespera::jni_app!(create_app);
111+
```
112+
113+
Feature flags:
114+
115+
| Feature | Re-exports | Adds |
116+
|---------|-----------|------|
117+
| `inprocess` | `vespera::inprocess` (= `vespera_inprocess`) | dispatch, register_app, envelopes |
118+
| `jni` | `vespera::jni` (= `vespera_jni`) + implies `inprocess` | RUNTIME, jni_app!, JNI symbol |
119+
120+
## JNI ARCHITECTURE
121+
122+
```
123+
Java (Spring Boot) Rust (cdylib) vespera crates
124+
───────────────── ────────────── ─────────────────
125+
VesperaBridge.init() → JNI_OnLoad vespera_inprocess::register_app()
126+
↓ ↓
127+
VesperaBridge.dispatch() → JNI symbol vespera_inprocess::dispatch_from_json()
128+
↓ ↓ ↓
129+
VesperaProxyController catch_unwind router.oneshot(request)
130+
↓ ↓ ↓
131+
ResponseEntity JSON envelope axum handlers
132+
```
133+
134+
### Rust side (example app — 2 lines of JNI code):
135+
```rust
136+
pub fn create_app() -> axum::Router { vespera!(...) }
137+
vespera::jni_app!(create_app);
138+
```
139+
140+
### Java side (user app — 1 meaningful line):
141+
```java
142+
VesperaBridge.init("rust_jni_demo");
143+
SpringApplication.run(DemoApplication.class, args);
144+
```
44145

45146
## SCHEMA_TYPE! MACRO
46147

47148
Generate request/response types from existing structs with powerful transformations.
48149

49150
### Key Features
50-
- **Same-file Model reference**: `schema_type!(Schema from Model, name = "UserSchema")` - infers module path from file location
151+
- **Same-file Model reference**: `schema_type!(Schema from Model, name = "UserSchema")`
51152
- **Cross-file reference**: `schema_type!(Response from crate::models::user::Model, omit = ["password"])`
52153
- **SeaORM integration**: Automatic conversion of `HasOne`, `BelongsTo`, `HasMany` relations
53154
- **Chrono conversion**: `DateTimeWithTimeZone``vespera::chrono::DateTime<FixedOffset>`
@@ -66,55 +167,50 @@ Generate request/response types from existing structs with powerful transformati
66167
| `rename_all` | Serde rename strategy |
67168
| `ignore` | Skip Schema derive |
68169

69-
### Module Path Resolution
70-
When using simple `Model` path (no `crate::` prefix):
71-
1. `find_struct_from_path()` calls `find_struct_by_name_in_all_files()`
72-
2. Uses `schema_name` hint to disambiguate (e.g., "UserSchema" → prefers `user.rs`)
73-
3. `file_path_to_module_path()` infers module path from file location
74-
4. This enables `super::` resolution in relation types
75-
76170
## CONVENTIONS
77171

78172
- **Rust 2024 edition** across all crates
79173
- **Workspace dependencies**: Internal crates use `{ workspace = true }`
80-
- **Version sync**: All crates at 0.1.19
81174
- **Test frameworks**: `rstest` for unit tests, `insta` for snapshots
82175
- **No `build.rs`**: All code gen via proc-macros at compile time
176+
- **No direct axum dep in examples**: Use `vespera::axum` re-export
177+
- **No direct vespera_jni/vespera_inprocess dep**: Use `vespera` features
178+
- **Java package**: `com.devfive.vespera.bridge` (fixed for JNI symbol stability)
179+
- **Java build**: Gradle (Kotlin DSL), published to GitHub Packages
83180

84181
## ANTI-PATTERNS (THIS PROJECT)
85182

86183
- **NEVER** add `build.rs` - macro handles compile-time generation
87184
- **NEVER** manually register routes - `vespera!` macro discovers them
88185
- **NEVER** write OpenAPI JSON by hand - generated from code
186+
- **NEVER** write JNI boilerplate in examples - use `vespera::jni_app!` macro
187+
- **NEVER** parse domain JSON in Java - Spring is a proxy, Rust owns business logic
188+
- **NEVER** depend on axum directly in examples - use `vespera::axum`
189+
- **NEVER** depend on `vespera_jni` or `vespera_inprocess` directly - use `vespera` features
190+
- **NEVER** put transport logic in vespera core - use `vespera_inprocess` / `vespera_jni`
89191
- Route functions **MUST** be `pub async fn`
90192

91-
## ARCHITECTURE FLOW
92-
93-
```
94-
User writes: vespera!() macro at compile-time:
95-
┌──────────────┐ ┌────────────────────────────────────────┐
96-
│ src/routes/ │ ──── │ 1. Scan filesystem for .rs files │
97-
│ users.rs │ │ 2. Parse #[route] attributes │
98-
│ posts.rs │ │ 3. Extract handler signatures │
99-
└──────────────┘ │ 4. Generate Axum Router code │
100-
│ 5. Build OpenAPI spec │
101-
│ 6. Write openapi.json (optional) │
102-
│ 7. Inject Swagger/ReDoc routes │
103-
└────────────────────────────────────────┘
104-
```
105-
106193
## COMMANDS
107194

108195
```bash
109196
# Development
110197
cargo build # Build all crates
111198
cargo test --workspace # Run all tests
112199
cargo test -p vespera_macro # Test macros only
200+
cargo test -p rust-jni-demo # Test JNI demo
113201

114-
# Run example
202+
# Run axum example
115203
cd examples/axum-example
116204
cargo run # Starts server on :3000
117-
# Visit http://localhost:3000/docs for Swagger UI
205+
206+
# Run JNI demo (standalone Rust server)
207+
cargo run -p rust-jni-demo # Starts server on :3000
208+
209+
# Run JNI demo (Java + Rust)
210+
cd libs/vespera-bridge && ./gradlew jar
211+
cargo build -p rust-jni-demo --release
212+
cd examples/rust-jni-demo/java && ./gradlew :demo-app:bootJar
213+
java -jar demo-app/build/libs/demo-app-0.1.0.jar
118214

119215
# Check generated OpenAPI
120216
cat examples/axum-example/openapi.json
@@ -126,3 +222,5 @@ cat examples/axum-example/openapi.json
126222
- OpenAPI files are **regenerated on every build** when `openapi = "..."` specified
127223
- `CARGO_MANIFEST_DIR` env var used to locate `src/routes/` folder
128224
- Generic types in schemas require `#[derive(Schema)]` on all type params
225+
- JNI native library can be bundled inside the fat JAR for single-file deployment
226+
- `VesperaBridge.init()` auto-extracts bundled native lib to temp, falls back to system path

0 commit comments

Comments
 (0)