Skip to content
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
1 change: 1 addition & 0 deletions .changepacks/changepack_log_QFdRU4xGy6yo_gwqGDegd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"libs/vespera-bridge/build.gradle.kts":"Minor","Cargo.toml":"Patch"},"note":"Initial","date":"2026-03-21T07:48:00.475937500Z"}
2 changes: 1 addition & 1 deletion .changepacks/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"ignore": ["**", "!crates/**"],
"ignore": ["**", "!crates/**", "!libs/**"],
"baseBranch": "main",
"latestPackage": "crates/vespera/Cargo.toml",
"publish": {}
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,29 @@ jobs:
publish: true
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

# publish vespera-bridge Java library to GitHub Packages
publish-java:
name: Publish vespera-bridge
runs-on: ubuntu-latest
needs:
- changepacks
if: contains(needs.changepacks.outputs.changepacks, 'libs/vespera-bridge')
permissions:
packages: write
steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- uses: gradle/actions/setup-gradle@v4

- name: Publish to GitHub Packages
working-directory: libs/vespera-bridge
run: ./gradlew publish
env:
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ build_rs_cov.profraw
.claude
.DS_Store
coverage-report

# Java build artifacts
*.class
.gradle/
build/
bin/

# IDE
.settings/
.classpath
.project
*.iml
.idea/
160 changes: 129 additions & 31 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
# VESPERA PROJECT KNOWLEDGE BASE

**Generated:** 2026-02-04
**Generated:** 2026-03-21
**Branch:** main

## OVERVIEW

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.

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.

## STRUCTURE

```
vespera/
├── crates/
│ ├── vespera/ # Public API - re-exports everything (+ chrono re-export)
│ ├── vespera_core/ # OpenAPI types, route/schema abstractions
│ └── vespera_macro/ # Proc-macros (main logic lives here)
└── examples/axum-example/ # Demo app with route patterns
│ ├── vespera/ # Public API - re-exports everything
│ │ └── src/lib.rs # Core re-exports (no transport deps)
│ ├── vespera_core/ # OpenAPI types, route/schema abstractions
│ ├── vespera_macro/ # Proc-macros (main logic lives here)
│ ├── vespera_inprocess/ # In-process dispatch (transport-agnostic)
│ │ └── src/lib.rs # dispatch(), register_app(), dispatch_from_json()
│ └── vespera_jni/ # JNI bridge (depends on vespera_inprocess)
│ └── src/lib.rs # RUNTIME, jni_app! macro, JNI symbol export
├── libs/
│ └── vespera-bridge/ # Java library (com.devfive.vespera.bridge)
│ ├── VesperaBridge.java # JNI native loader + dispatch
│ └── VesperaProxyController.java # Auto-configured Spring proxy
├── examples/
│ ├── axum-example/ # Standard axum server demo
│ └── rust-jni-demo/ # JNI + standalone server demo
│ ├── src/ # Rust: routes, create_app(), jni_app!
│ └── java/demo-app/ # Java: Spring Boot proxy
```

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

## KEY COMPONENTS

Expand All @@ -41,13 +62,93 @@ vespera/
| `vespera_macro/src/parser/parameters.rs` | ~845 | Extract path/query params from handlers |
| `vespera_macro/src/openapi_generator.rs` | ~808 | OpenAPI doc assembly |
| `vespera_macro/src/collector.rs` | ~707 | Filesystem route scanning |
| `vespera_inprocess/src/lib.rs` | ~175 | In-process dispatch + app factory |
| `vespera_jni/src/lib.rs` | ~95 | JNI RUNTIME + jni_app! macro + JNI symbol |

## CRATE DEPENDENCY GRAPH

```
vespera (OpenAPI framework)
├── vespera_core
├── vespera_macro
├── vespera_inprocess (optional, feature = "inprocess")
└── vespera_jni (optional, feature = "jni", implies "inprocess")

vespera_inprocess (transport layer — no JNI deps)
├── axum (direct — owns Router re-export)
├── http, http-body-util, tower
├── serde, serde_json
└── tokio (rt only — for dispatch_from_json Runtime param)

vespera_jni (JNI glue — thin layer)
├── vespera_inprocess (via workspace)
├── jni
└── tokio (rt-multi-thread — for LazyLock<Runtime>)

rust-jni-demo (example — depends on vespera ONLY)
└── vespera = { features = ["jni"] }
```

## USER-FACING API

Users depend on `vespera` only. Internal crates are never depended on directly.

```toml
# Cargo.toml — the only dependency needed
[dependencies]
vespera = { version = "...", features = ["jni"] }
```

```rust
// lib.rs — all imports come from vespera
use vespera::{axum, vespera};

pub fn create_app() -> axum::Router {
vespera!(title = "My API", version = "1.0.0")
}

vespera::jni_app!(create_app);
```

Feature flags:

| Feature | Re-exports | Adds |
|---------|-----------|------|
| `inprocess` | `vespera::inprocess` (= `vespera_inprocess`) | dispatch, register_app, envelopes |
| `jni` | `vespera::jni` (= `vespera_jni`) + implies `inprocess` | RUNTIME, jni_app!, JNI symbol |

## JNI ARCHITECTURE

```
Java (Spring Boot) Rust (cdylib) vespera crates
───────────────── ────────────── ─────────────────
VesperaBridge.init() → JNI_OnLoad vespera_inprocess::register_app()
↓ ↓
VesperaBridge.dispatch() → JNI symbol vespera_inprocess::dispatch_from_json()
↓ ↓ ↓
VesperaProxyController catch_unwind router.oneshot(request)
↓ ↓ ↓
ResponseEntity JSON envelope axum handlers
```

### Rust side (example app — 2 lines of JNI code):
```rust
pub fn create_app() -> axum::Router { vespera!(...) }
vespera::jni_app!(create_app);
```

### Java side (user app — 1 meaningful line):
```java
VesperaBridge.init("rust_jni_demo");
SpringApplication.run(DemoApplication.class, args);
```

## SCHEMA_TYPE! MACRO

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

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

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

## CONVENTIONS

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

## ANTI-PATTERNS (THIS PROJECT)

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

## ARCHITECTURE FLOW

```
User writes: vespera!() macro at compile-time:
┌──────────────┐ ┌────────────────────────────────────────┐
│ src/routes/ │ ──── │ 1. Scan filesystem for .rs files │
│ users.rs │ │ 2. Parse #[route] attributes │
│ posts.rs │ │ 3. Extract handler signatures │
└──────────────┘ │ 4. Generate Axum Router code │
│ 5. Build OpenAPI spec │
│ 6. Write openapi.json (optional) │
│ 7. Inject Swagger/ReDoc routes │
└────────────────────────────────────────┘
```

## COMMANDS

```bash
# Development
cargo build # Build all crates
cargo test --workspace # Run all tests
cargo test -p vespera_macro # Test macros only
cargo test -p rust-jni-demo # Test JNI demo

# Run example
# Run axum example
cd examples/axum-example
cargo run # Starts server on :3000
# Visit http://localhost:3000/docs for Swagger UI

# Run JNI demo (standalone Rust server)
cargo run -p rust-jni-demo # Starts server on :3000

# Run JNI demo (Java + Rust)
cd libs/vespera-bridge && ./gradlew jar
cargo build -p rust-jni-demo --release
cd examples/rust-jni-demo/java && ./gradlew :demo-app:bootJar
java -jar demo-app/build/libs/demo-app-0.1.0.jar

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