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 .bazelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.direnv
58 changes: 6 additions & 52 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,61 +11,15 @@ concurrency:
cancel-in-progress: true

jobs:
check:
name: Check & Test
bazel:
name: Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: DeterminateSystems/nix-installer-action@main

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: DeterminateSystems/magic-nix-cache-action@main

- name: Cargo fmt
run: cargo fmt --all -- --check

- name: Clippy
run: cargo clippy --workspace -- -D warnings

- name: Tests
run: cargo test --workspace

build-bindings:
name: Build Bindings
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Build core
run: cargo build -p stdbr-core

- name: Build ffi-c
run: cargo build -p stdbr-ffi

- name: Build nodejs
run: cargo build -p stdbr-napi

- name: Build python
run: cargo build -p stdbr-python

- name: Build wasm
run: cargo build -p stdbr-wasm --target wasm32-unknown-unknown
- name: Build & Test
run: nix develop --command bazel test //...
20 changes: 5 additions & 15 deletions .github/workflows/ibge-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,21 @@ name: IBGE Sync Check

on:
schedule:
# Runs daily at 06:00 UTC
- cron: "0 6 * * *"
workflow_dispatch: # Allow manual trigger
workflow_dispatch:

jobs:
ibge-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable
- uses: DeterminateSystems/nix-installer-action@main

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-ibge-${{ hashFiles('**/Cargo.lock') }}

- name: Generate golden.json (fetches IBGE data from API)
run: cargo run -p parity-gen > tests/parity/golden.json
- uses: DeterminateSystems/magic-nix-cache-action@main

- name: Run parity tests (includes IBGE sync validation)
run: cargo test -p stdbr-core --test parity
- name: IBGE sync test
run: nix develop --command bazel test //tests/parity:parity_rust

- name: Open issue if out of sync
if: failure()
Expand Down
43 changes: 29 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
Requires [Nix](https://nixos.org/) with flakes enabled.

```bash
# enter dev shell
direnv allow # or: nix develop
```

Expand All @@ -15,42 +14,58 @@ direnv allow # or: nix develop
cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --check
```

## Bazel

```bash
build # alias: bazel build //...
test # alias: bazel test //...
check # alias: bazel test //...
```

## Format
## Parity tests

Every module must produce identical results across all bindings. The `tests/parity/` suite generates a `golden.json` from `stdbr-core` and validates Rust, Node.js, Python, FFI-C, and WASM against it.

```bash
fmt # alias: nix fmt
bazel test //tests/parity/...
```

## Building specific bindings
When adding a new function to core, add the corresponding test cases to `tools/parity_gen/src/main.rs` and update every `test_parity.*` file.

```bash
cargo build -p stdbr-python
cargo build -p stdbr-wasm --target wasm32-unknown-unknown
cd bindings/python && maturin develop
cd bindings/wasm && wasm-pack build --target web
```
## Adding a new binding target

1. Create `bindings/<target>/` with `Cargo.toml` and source
2. Expose the same API surface as existing bindings (parse, validate, format, generate)
3. Add `test_parity.*` that reads `golden.json` and validates all cases
4. Add build + test rules in `tests/parity/BUILD.bazel`
5. Register the Cargo manifest in `MODULE.bazel`

## Adding a new BR module

1. Implement in `core/src/<module>.rs` (`no_std` + `alloc`)
2. Export from `core/src/lib.rs`
3. Add bindings in all targets: `ffi-c`, `nodejs`, `python`, `wasm`
4. Add golden test cases in `tools/parity_gen/src/main.rs`
5. Update all `test_parity.*` files to cover the new module
6. If the module needs external data (like municipio uses IBGE), add a sync workflow in `.github/workflows/`

## Project structure

```
stdbr/
core/ # stdbr-core (no_std Rust library)
bindings/
ffi-c/ # C/C++ FFI (cdylib + staticlib + cbindgen)
ffi-c/ # C/C++ FFI (staticlib + cbindgen)
nodejs/ # Node.js via napi-rs
python/ # Python via PyO3 + maturin
wasm/ # WebAssembly via wasm-bindgen
tools/ # Bazel custom rules
.github/workflows/ # CI (Nix-based)
tools/
parity_gen/ # Golden test data generator
rules_rust_extras/ # Bazel custom rules (cbindgen)
tests/parity/ # Cross-binding parity tests
.github/workflows/ # CI + IBGE sync
flake.nix # Nix dev environment
MODULE.bazel # Bazel module config
```
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module(name = "stdbr", version = "0.0.1")

bazel_dep(name = "rules_rust", version = "0.70.0")
bazel_dep(name = "rules_rust_wasm_bindgen", version = "0.70.0")
bazel_dep(name = "rules_python", version = "1.6.3")
bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "platforms", version = "1.0.0")
Expand Down
52 changes: 27 additions & 25 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/fullzer4/stdbr/actions/workflows/ci.yml/badge.svg)](https://github.com/fullzer4/stdbr/actions/workflows/ci.yml)
[![IBGE Sync](https://github.com/fullzer4/stdbr/actions/workflows/ibge-sync.yml/badge.svg)](https://github.com/fullzer4/stdbr/actions/workflows/ibge-sync.yml)
[![License: Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](LICENSE)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

Standard library for Brazil.

Expand Down
30 changes: 30 additions & 0 deletions bindings/nodejs/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
load("@rules_rust//rust:defs.bzl", "rust_shared_library")
load("@rules_rust//cargo:defs.bzl", "cargo_build_script")

exports_files(["Cargo.toml", "test_parity.js"] + glob(["src/**/*.rs"]))

cargo_build_script(
name = "napi_build_script",
srcs = ["build.rs"],
deps = ["@crates//:napi-build"],
)

rust_shared_library(
name = "stdbr-napi",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
deps = [
"//core:stdbr-core",
"@crates//:napi",
":napi_build_script",
],
proc_macro_deps = ["@crates//:napi-derive"],
visibility = ["//visibility:public"],
)

genrule(
name = "stdbr-node",
srcs = [":stdbr-napi"],
outs = ["stdbr.node"],
cmd = "cp $< $@",
visibility = ["//visibility:public"],
)
2 changes: 1 addition & 1 deletion bindings/nodejs/test_parity.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const goldenPath = process.env.GOLDEN_JSON || join(__dirname, "../../tests/parit
const golden = JSON.parse(readFileSync(goldenPath, "utf8"));

const require = createRequire(import.meta.url);
const stdbr = require("./stdbr.node");
const stdbr = require(process.env.STDBR_NODE || join(__dirname, "stdbr.node"));
const {
Cpf, cpfIsValid, cpfIsValidStrict, cpfFormat, cpfRemoveSymbols,
cpfGenerate, cpfComputeCheckDigits,
Expand Down
18 changes: 18 additions & 0 deletions bindings/python/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
load("@rules_rust//rust:defs.bzl", "rust_shared_library")

exports_files(["Cargo.toml", "test_parity.py"] + glob(["src/**/*.rs"]))

rust_shared_library(
name = "stdbr-python",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
deps = ["//core:stdbr-core", "@crates//:pyo3"],
visibility = ["//visibility:public"],
)

genrule(
name = "stdbr-pymodule",
srcs = [":stdbr-python"],
outs = ["stdbr.abi3.so"],
cmd = "cp $< $@",
visibility = ["//visibility:public"],
)
5 changes: 5 additions & 0 deletions bindings/python/test_parity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import json
import os
import sys
import unittest
from pathlib import Path

_pymodule = os.environ.get("STDBR_PYMODULE")
if _pymodule:
sys.path.insert(0, str(Path(_pymodule).resolve().parent))

import stdbr

GOLDEN_PATH = os.environ.get("GOLDEN_JSON") or str(
Expand Down
19 changes: 19 additions & 0 deletions bindings/wasm/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
load("@rules_rust//rust:defs.bzl", "rust_shared_library")
load("@rules_rust_wasm_bindgen//:defs.bzl", "rust_wasm_bindgen")

exports_files(["Cargo.toml", "test_parity.js"] + glob(["src/**/*.rs"]))

rust_shared_library(
name = "stdbr-wasm-cdylib",
srcs = glob(["src/**/*.rs"]),
crate_root = "src/lib.rs",
deps = ["//core:stdbr-core", "@crates//:wasm-bindgen"],
platform = "@rules_rust//rust/platform:wasm",
visibility = ["//visibility:public"],
)

rust_wasm_bindgen(
name = "stdbr-wasm",
wasm_file = ":stdbr-wasm-cdylib",
target = "nodejs",
visibility = ["//visibility:public"],
)
2 changes: 1 addition & 1 deletion bindings/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ crate-type = ["cdylib"]

[dependencies]
stdbr-core = { path = "../../core" }
wasm-bindgen = "0.2"
wasm-bindgen = "=0.2.105"

[package.metadata.wasm-pack.profile.release]
wasm-opt = false
Expand Down
4 changes: 2 additions & 2 deletions core/tests/parity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use stdbr_core::{cep, cnpj, cpf, municipio, uf};
fn golden() -> Value {
let path =
std::env::var("GOLDEN_JSON").unwrap_or_else(|_| "tests/parity/golden.json".to_string());
let json =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {path}: {e}"));
let json = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {path}: {e} (run via bazel test)"));
serde_json::from_str(&json).expect("failed to parse golden.json")
}

Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
category = "bazel";
}
{
name = "test";
name = "check";
command = "bazel test //...";
help = "Run all tests";
category = "bazel";
Expand Down
Loading
Loading