Skip to content

Commit e08ca55

Browse files
committed
wip
1 parent a27c4ff commit e08ca55

File tree

9 files changed

+178
-283
lines changed

9 files changed

+178
-283
lines changed

rust/README.md

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,40 @@
1-
# Rust Workspace
2-
3-
This workspace contains the code generator and runtime for working with BARE schemas and versioned data, plus a runnable example.
1+
# Rust
42

53
## Crates
64

7-
- `vbare-gen`: TokenStream code generator that parses `.bare` schemas and emits Rust types deriving `serde` and using `serde_bare` for encoding/decoding.
8-
- `vbare-compiler`: Build-script helper that processes a directory of schemas, writes one Rust file per schema into `OUT_DIR`, and emits a `combined_imports.rs` module to include from your crate.
9-
- `vbare`: Runtime traits for versioned data with helpers to serialize/deserialize across versions and with embedded version headers.
10-
- `examples/basic`: End-to-end example that generates types for three schema versions (v1/v2/v3) and shows migrations between them.
5+
- `vbare-gen`: Code generator that parses `.bare` schemas and emits Rust types
6+
- `vbare-compiler`: Build-script helper that processes a directory of schemas
7+
- `vbare`: Runtime traits for versioned data with helpers to serialize/deserialize across versions
118

12-
## Quick Start (use in your crate)
9+
## Quick Start
1310

14-
1) Add dependencies in your `Cargo.toml`:
11+
**Step 1: Add dependencies in your `Cargo.toml`:**
1512

1613
```toml
1714
[dependencies]
1815
anyhow = "1"
1916
serde = { version = "1", features = ["derive"] }
2017
serde_bare = "0.5"
21-
vbare = { path = "../vbare" } # adjust path as needed
18+
vbare = "FILL ME IN"
2219

2320
[build-dependencies]
2421
anyhow = "1"
25-
vbare-compiler = { path = "../vbare-compiler" } # or use vbare-gen directly
22+
vbare-compiler = "FILL ME IN"
2623
```
2724

28-
2) In `build.rs`, process your `.bare` schema files directory and generate the modules:
25+
**Step 2: In `build.rs`, process your `.bare` schema files directory and generate the modules:**
2926

3027
```rust
3128
use std::path::Path;
3229

3330
fn main() -> Result<(), Box<dyn std::error::Error>> {
3431
let schemas = Path::new("schemas");
35-
// Or `process_schemas_with_config(schemas, &vbare_compiler::Config::with_hashable_map())`.
3632
vbare_compiler::process_schemas(schemas)?;
3733
Ok(())
3834
}
3935
```
4036

41-
Note: If you prefer to call the generator directly (as in `examples/basic`), use `vbare-gen` from your `build.rs`, parse the returned `TokenStream` with `syn`, and format with `prettyplease`. In that case add `syn` and `prettyplease` to `[build-dependencies]`.
42-
43-
3) In your `lib.rs` or `mod.rs`, include the auto-generated module that re-exports all generated files:
37+
** Step 3: In your `lib.rs` or `mod.rs`, include the auto-generated module:**
4438

4539
```rust
4640
// Bring generated schemas into this crate
@@ -50,7 +44,7 @@ pub mod schemas {
5044
}
5145
```
5246

53-
4) Implement versioning (example with owned data):
47+
**Step 4: Implement versioning (example with owned data):**
5448

5549
```rust
5650
use anyhow::{bail, Result};
@@ -109,28 +103,6 @@ let bytes = MyTypeVersioned::latest(latest).serialize_with_embedded_version(2)?;
109103
let latest2 = MyTypeVersioned::deserialize_with_embedded_version(&bytes)?;
110104
```
111105

112-
## Example
113-
114-
See `rust/examples/basic/` for a full example:
115-
- `build.rs` normalizes the test fixture schemas and runs codegen (mirrors what `vbare-compiler` does).
116-
- `src/lib.rs` includes the generated `schemas` module and implements `OwnedVersionedData` for `AppVersioned` with v1→v2→v3 migrations.
117-
- `tests/migrator.rs` exercises up/down conversions and BARE encoding with `serde_bare`.
118-
119-
Run just the example crate’s tests:
120-
121-
```bash
122-
cargo test -p basic
123-
```
124-
125-
## Workspace
126-
127-
Build and test everything:
128-
129-
```bash
130-
cargo build
131-
cargo test
132-
```
133-
134106
## License
135107

136-
MIT OR Apache-2.0
108+
MIT

rust/examples/basic/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,5 @@ serde_bare = { workspace = true }
1414
vbare = { path = "../../vbare" }
1515

1616
[build-dependencies]
17-
anyhow = { workspace = true }
18-
prettyplease = { workspace = true }
19-
syn = { workspace = true }
20-
vbare-gen = { path = "../../vbare-gen" }
17+
vbare-compiler = { path = "../../vbare-compiler" }
2118

rust/examples/basic/build.rs

Lines changed: 4 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,7 @@
1-
use std::{
2-
env, fs,
3-
path::{Path, PathBuf},
4-
};
5-
6-
use anyhow::Result;
7-
8-
fn main() -> Result<()> {
9-
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
10-
11-
// Locate repo root from this crate: rust/examples/basic
12-
let mut repo_root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
13-
repo_root.pop(); // rust/examples/basic -> rust/examples
14-
repo_root.pop(); // rust/examples -> rust
15-
repo_root.pop(); // rust -> repo root
16-
// Now at repo root
17-
18-
let fixtures_dir = repo_root.join("fixtures/tests/basic");
19-
println!("cargo:rerun-if-changed={}", fixtures_dir.display());
20-
21-
// Normalize the schemas into a temp dir we control, as the fixtures use a slightly different
22-
// flavor than our grammar expects.
23-
let schema_dir = out_dir.join("normalized_schemas");
24-
fs::create_dir_all(&schema_dir)?;
25-
26-
for v in ["v1", "v2", "v3"] {
27-
let src = fixtures_dir.join(format!("{v}.bare"));
28-
let dst = schema_dir.join(format!("{v}.bare"));
29-
30-
let content_raw = fs::read_to_string(&src)?;
31-
32-
// Normalize fixture syntax to match current grammar:
33-
// - Strip '//' comments
34-
// - Insert missing 'type' for top-level enums (e.g., `enum X {` -> `type X enum {`)
35-
// - string -> str
36-
// - []Todo -> list<Todo>
37-
// - map<K, V> -> map<K><V>
38-
let mut normalized = String::new();
39-
for line in content_raw.lines() {
40-
let line_wo_comment = match line.find("//") {
41-
Some(i) => &line[..i],
42-
None => line,
43-
};
44-
let trimmed = line_wo_comment.trim_start();
45-
let converted = if trimmed.starts_with("enum ") {
46-
let rest = &trimmed["enum ".len()..];
47-
if let Some(brace_idx) = rest.find('{') {
48-
let name = rest[..brace_idx].trim();
49-
format!("type {name} enum {{")
50-
} else {
51-
line_wo_comment.to_string()
52-
}
53-
} else {
54-
line_wo_comment.to_string()
55-
};
56-
if !converted.trim().is_empty() {
57-
normalized.push_str(&converted);
58-
normalized.push('\n');
59-
}
60-
}
61-
let mut normalized = normalized
62-
.replace("string", "str")
63-
.replace("[]Todo", "list<Todo>")
64-
.replace("map<str, str>", "map<str><str>")
65-
.replace("map<TodoId, Todo>", "map<TodoId><Todo>")
66-
.replace("map<TagId, Tag>", "map<TagId><Tag>")
67-
.replace("map<BoardId, Board>", "map<BoardId><Board>")
68-
.replace("map<str, list<TodoId>>", "map<str><list<TodoId>>");
69-
70-
if v == "v3" {
71-
// Ensure ChangeKind enum is defined before Change struct
72-
let mut lines_all: Vec<&str> = normalized.lines().collect();
73-
74-
fn extract_block<'a>(
75-
lines: &mut Vec<&'a str>,
76-
start_pred: &str,
77-
) -> Option<Vec<&'a str>> {
78-
let start = lines
79-
.iter()
80-
.position(|l| l.trim_start().starts_with(start_pred))?;
81-
let mut end = start;
82-
let mut brace_count = 0i32;
83-
let mut seen_open = false;
84-
for i in start..lines.len() {
85-
let l = lines[i];
86-
if l.contains('{') {
87-
brace_count += 1;
88-
seen_open = true;
89-
}
90-
if l.contains('}') {
91-
brace_count -= 1;
92-
}
93-
end = i;
94-
if seen_open && brace_count == 0 {
95-
break;
96-
}
97-
}
98-
let block: Vec<&str> = lines[start..=end].to_vec();
99-
lines.drain(start..=end);
100-
Some(block)
101-
}
102-
103-
let change_block = extract_block(&mut lines_all, "type Change struct");
104-
let kind_block = extract_block(&mut lines_all, "type ChangeKind enum");
105-
106-
if change_block.is_some() && kind_block.is_some() {
107-
let insert_at = lines_all
108-
.iter()
109-
.position(|l| l.trim_start().starts_with("type Todo struct"))
110-
.unwrap_or(lines_all.len());
111-
let mut rebuilt: Vec<&str> = Vec::new();
112-
rebuilt.extend_from_slice(&lines_all[..insert_at]);
113-
for l in kind_block.unwrap() {
114-
rebuilt.push(l);
115-
}
116-
for l in change_block.unwrap() {
117-
rebuilt.push(l);
118-
}
119-
rebuilt.extend_from_slice(&lines_all[insert_at..]);
120-
normalized = rebuilt.join("\n");
121-
}
122-
}
123-
124-
fs::write(&dst, normalized)?;
125-
}
126-
127-
// Generate Rust from schemas: write one file per schema + combined_imports.rs
128-
let out_path = &out_dir;
129-
let mut all_names = Vec::new();
130-
for entry in fs::read_dir(&schema_dir)?.flatten() {
131-
let path = entry.path();
132-
if path.is_dir() {
133-
continue;
134-
}
135-
let bare_name = path
136-
.file_name()
137-
.and_then(|s| s.to_str())
138-
.and_then(|s| s.rsplit_once('.'))
139-
.map(|(n, _)| n)
140-
.expect("valid file name");
141-
142-
// Use HashMap instead of rivet_util::HashableMap to avoid extra dependency here.
143-
let tokens = vbare_gen::bare_schema(
144-
&path,
145-
vbare_gen::Config {
146-
use_hashable_map: false,
147-
},
148-
);
149-
let ast = syn::parse2(tokens).expect("parse generated code");
150-
let content = prettyplease::unparse(&ast);
151-
fs::write(out_path.join(format!("{bare_name}_generated.rs")), content)?;
152-
all_names.push(bare_name.to_string());
153-
}
154-
155-
let mut mod_content = String::from("// Auto-generated module file for schemas\n\n");
156-
for name in all_names {
157-
mod_content.push_str(&format!(
158-
"pub mod {name} {{\n include!(concat!(env!(\"OUT_DIR\"), \"/{name}_generated.rs\"));\n}}\n"
159-
));
160-
}
161-
fs::write(out_path.join("combined_imports.rs"), mod_content)?;
1+
use std::path::Path;
1622

3+
fn main() -> Result<(), Box<dyn std::error::Error>> {
4+
let schemas = Path::new("schemas");
5+
vbare_compiler::process_schemas(schemas)?;
1636
Ok(())
1647
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type Todo struct {
2+
id: u32
3+
title: str
4+
done: bool
5+
}
6+
type App struct {
7+
todos: list<Todo>
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type TodoId u64
2+
type TodoStatus enum {
3+
OPEN
4+
IN_PROGRESS
5+
DONE
6+
}
7+
type Todo struct {
8+
id: TodoId
9+
title: str
10+
status: TodoStatus
11+
created_at: u64
12+
tags: list<str>
13+
}
14+
type App struct {
15+
todos: map<TodoId><Todo>
16+
settings: map<str><str>
17+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
type TodoId u64
2+
type UserId u64
3+
type TeamId u64
4+
type TagId u32
5+
type BoardId u32
6+
type TodoStatus enum {
7+
OPEN
8+
IN_PROGRESS
9+
DONE
10+
}
11+
type Priority enum {
12+
LOW
13+
MEDIUM
14+
HIGH
15+
CRITICAL
16+
}
17+
type AssigneeKind enum {
18+
NONE
19+
USER
20+
TEAM
21+
}
22+
type Assignee struct {
23+
kind: AssigneeKind
24+
user_id: optional<UserId>
25+
team_id: optional<TeamId>
26+
}
27+
type Tag struct {
28+
id: TagId
29+
name: str
30+
color: optional<str>
31+
}
32+
type TodoDetail struct {
33+
title: str
34+
tags: map<TagId><Tag>
35+
}
36+
type ChangeKind enum {
37+
CREATED
38+
UPDATED
39+
STATUS_CHANGED
40+
ASSIGNED
41+
TAGGED
42+
}
43+
type Change struct {
44+
at: u64
45+
kind: ChangeKind
46+
}
47+
type Todo struct {
48+
id: TodoId
49+
status: TodoStatus
50+
created_at: u64
51+
priority: Priority
52+
assignee: Assignee
53+
detail: TodoDetail
54+
history: list<Change>
55+
}
56+
type Theme enum {
57+
LIGHT
58+
DARK
59+
SYSTEM
60+
}
61+
type AppConfig struct {
62+
theme: Theme
63+
features: map<str><bool>
64+
}
65+
type Board struct {
66+
id: BoardId
67+
name: str
68+
columns: map<str><list<TodoId>>
69+
}
70+
type App struct {
71+
todos: map<TodoId><Todo>
72+
config: AppConfig
73+
boards: map<BoardId><Board>
74+
}

0 commit comments

Comments
 (0)