Thank you for your interest in contributing to tinyxml2-rs! Every contribution matters — whether it's a bug report, a feature suggestion, a documentation improvement, or a code change.
- Code of Conduct
- How to Contribute
- Development Setup
- Code Style
- Testing
- Pull Request Process
- Commit Convention
- Architecture Reference
- Getting Help
This project follows the Contributor Covenant Code of Conduct. By participating, you agree to uphold a welcoming, inclusive, and harassment-free environment for everyone.
Found a bug? We appreciate you taking the time to report it.
- Search existing issues to check if it's already been reported.
- Open a new issue using the bug report template.
- Include the following information:
- A clear, descriptive title
- Steps to reproduce the issue
- Expected behavior vs. actual behavior
- The XML input that triggers the problem (if applicable)
- Your Rust version (
rustc --version) and OS - Any relevant error messages or stack traces
Security vulnerabilities should be reported privately. See SECURITY.md.
Have an idea for an improvement?
- Check the ROADMAP.md to see if it's already planned.
- Search existing issues for similar suggestions.
- Open a new issue using the feature request template.
- Describe:
- The problem your feature would solve
- Your proposed solution or API sketch
- Any alternatives you've considered
- How it relates to TinyXML2 behavioral compatibility
Ready to write code? Great! Here's the workflow:
- Fork the repository.
- Create a feature branch from
main(git checkout -b feat/my-feature). - Make your changes following the guidelines below.
- Commit using conventional commits.
- Push to your fork and open a pull request.
- Rust (stable toolchain — managed via
rust-toolchain.toml) - Git
# Clone your fork
git clone https://github.com/<your-username>/tinyxml2-rs.git
cd tinyxml2-rs
# Build the entire workspace
cargo build
# Run all tests
cargo test --workspace
# Run clippy lints
cargo clippy --workspace --all-targets -- -D warnings
# Check formatting
cargo fmt --all -- --check| Command | Description |
|---|---|
cargo build |
Build all crates |
cargo test --workspace |
Run all tests across the workspace |
cargo clippy --workspace --all-targets -- -D warnings |
Run lint checks |
cargo fmt --all |
Format all code |
cargo fmt --all -- --check |
Check formatting without modifying files |
cargo doc --workspace --no-deps --open |
Build and open documentation |
cargo bench -p tinyxml2-bench |
Run benchmarks |
We enforce consistent code style across the project:
- All code must be formatted with
rustfmtusing the project's rustfmt.toml. - Run
cargo fmt --allbefore committing. - CI will reject PRs with formatting issues.
- All code must pass
clippywith zero warnings. - Run
cargo clippy --workspace --all-targets -- -D warnings. - If you believe a clippy lint is a false positive, discuss it in your PR before adding an
#[allow(...)].
- Prefer safe Rust. The core
tinyxml2crate must contain nounsafecode. Thetinyxml2-capicrate may useunsafewhere required for FFI boundaries. - Write idiomatic Rust. Leverage the type system, use
Resultfor fallible operations, and prefer iterators over manual loops. - Document public APIs. Every public type, function, and method must have a doc comment (
///). - Keep functions focused. Each function should do one thing well.
- Use meaningful names. Variable and function names should be descriptive and self-documenting.
- Preserve existing comments. Don't remove or alter comments unrelated to your change.
Testing is critical for a library that targets behavioral compatibility. We maintain several layers of tests:
| Category | Location | Purpose |
|---|---|---|
| Unit tests | #[cfg(test)] modules in source files |
Test individual functions and types |
| Integration tests | tests/ |
Test cross-module behavior |
| Conformance tests | spec/ |
Verify behavioral parity with TinyXML2 |
| Fuzz tests | fuzz/ |
Find crashes and edge cases via random input |
- All new code must have tests. Aim for comprehensive coverage of both success and failure paths.
- All existing tests must pass. Run
cargo test --workspaceand ensure zero failures. - Bug fixes must include a regression test. Add a test that fails without your fix and passes with it.
- Conformance tests are particularly important. If your change affects parsing or output behavior, add or update conformance tests in
spec/.
# Run all tests
cargo test --workspace
# Run tests for a specific crate
cargo test -p tinyxml2
# Run a specific test by name
cargo test --workspace -- test_name
# Run tests with output
cargo test --workspace -- --nocaptureFuzz testing feeds random/mutated inputs to find crashes, panics, and invariant violations that unit tests miss. We use cargo-fuzz (libFuzzer) with 4 targets in the fuzz/ directory.
Fuzz targets require a nightly Rust toolchain and a C++11-capable compiler (GCC or Clang) for the sanitizer runtime.
# Install the nightly toolchain (one-time)
rustup toolchain install nightly
# Optionally, set nightly as the default for this project
rustup override set nightly
# Install cargo-fuzz (one-time)
cargo install cargo-fuzz| Target | What it tests |
|---|---|
parse_fuzz |
Feed arbitrary byte sequences to Document::parse_bytes. Must never panic. |
roundtrip_fuzz |
Parse → serialize → parse → assert output is stable (idempotent round-trip). |
serialize_fuzz |
Parse valid XML, then run to_string and to_string_compact. Must never panic. |
streaming_fuzz |
Feed random sequences of XmlPrinter method calls. Must never panic. |
All commands run from the fuzz/ directory:
cd fuzz
# Run a single target (runs indefinitely — Ctrl+C to stop)
cargo fuzz run parse_fuzz
# Run with a time limit (recommended for PR validation)
cargo fuzz run parse_fuzz -- -max_total_time=300
# Run other targets
cargo fuzz run roundtrip_fuzz
cargo fuzz run serialize_fuzz
cargo fuzz run streaming_fuzz
# Run all four targets sequentially (5 minutes each)
for target in parse_fuzz roundtrip_fuzz serialize_fuzz streaming_fuzz; do
echo "=== Fuzzing $target ==="
cargo fuzz run "$target" -- -max_total_time=300
doneUseful flags (passed after --):
| Flag | Purpose |
|---|---|
-max_total_time=N |
Stop after N seconds |
-max_len=N |
Limit input size to N bytes |
-jobs=N |
Run N parallel fuzzer jobs |
-dict=path/to/dict |
Use a dictionary of known tokens |
-rss_limit_mb=N |
Limit memory usage per job to N MB |
When a fuzzer finds a crash, it saves the crashing input as an artifact in artifacts/<target>/.
# Reproduce the crash with the saved artifact
cargo fuzz run parse_fuzz artifacts/parse_fuzz/<crash-file>
# Minimize the crashing input (find the smallest input that still triggers the bug)
cargo fuzz tmin parse_fuzz artifacts/parse_fuzz/<crash-file>The output will show the panic message, stack trace, and exact input bytes that triggered the crash. Use this to write a minimal reproduction as a unit test, then fix the bug.
For a quick smoke test before opening a PR: 5 minutes per target (20 minutes total) is the minimum.
For changes to parsing logic, serialization, or the XmlPrinter API: run the relevant target for at least 30 minutes. These are the highest-risk areas where subtle edge cases hide.
For a new release: run all 4 targets for 2+ hours each (overnight is ideal). Fuzzing is embarrassingly parallel — run each target in its own terminal:
# Terminal 1
cd fuzz && cargo fuzz run parse_fuzz -- -max_total_time=7200
# Terminal 2
cd fuzz && cargo fuzz run roundtrip_fuzz -- -max_total_time=7200
# Terminal 3
cd fuzz && cargo fuzz run serialize_fuzz -- -max_total_time=7200
# Terminal 4
cd fuzz && cargo fuzz run streaming_fuzz -- -max_total_time=7200Tip: If you're hunting for a known class of bug, create a dictionary file with XML tokens (
<,>,</,/>,<?xml,<!--,<![CDATA[, entity references) to help the fuzzer generate structurally valid XML faster.
-
Ensure all checks pass locally before opening a PR:
cargo build --workspacecargo test --workspacecargo clippy --workspace --all-targets -- -D warningscargo fmt --all -- --check
-
Fill out the PR template completely:
- Describe what the PR does and why
- Reference any related issues (
Fixes #123,Closes #456) - Note any breaking changes
-
Keep PRs focused. One logical change per PR. If you find unrelated issues, open separate PRs.
-
Respond to review feedback promptly. Discussions are how we improve the code together.
-
Squash commits if requested. We prefer a clean commit history on
main.
- Code compiles without warnings
- All tests pass (
cargo test --workspace) - Clippy passes with no warnings
- Code is formatted with
rustfmt - Public APIs have doc comments
- New functionality has tests
- Relevant documentation is updated
- Commit messages follow conventional commit format
We follow the Conventional Commits specification:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
| Type | When to Use |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation changes only |
style |
Formatting, whitespace — no code logic changes |
refactor |
Code change that neither fixes a bug nor adds a feature |
perf |
A code change that improves performance |
test |
Adding or updating tests |
build |
Changes to the build system or dependencies |
ci |
Changes to CI configuration |
chore |
Other changes that don't modify src or test files |
Use the crate name as the scope: core, capi, bench, or workspace for cross-cutting changes.
feat(core): implement arena-based node allocation
fix(core): handle self-closing tags with whitespace
docs: update README with quick start example
test(core): add conformance tests for entity decoding
refactor(capi): simplify FFI error conversion
Before contributing, familiarize yourself with the project's architecture:
- ARCHITECTURE.md — Detailed overview of the crate structure, module layout, and design decisions.
- ROADMAP.md — Current development phase and planned milestones.
- Generational Arena: The DOM uses a generational arena for node storage. Nodes are referenced by
NodeIdhandles rather than pointers. - Behavioral Compatibility: We treat TinyXML2 as a specification. When in doubt about behavior, match what TinyXML2 does.
- Recursive Descent Parser: The parser is hand-written, not generated. It mirrors TinyXML2's parsing strategy.
- Safe FFI Boundary: The
tinyxml2-capicrate contains theunsafeboundary. Everything else stays safe.
- Questions? Open a Discussion on GitHub.
- Stuck on a contribution? Mention it in your PR or issue — we're happy to help.
- Security concerns? See SECURITY.md.
Thank you for helping make tinyxml2-rs better! 🦀