fiscal-rsfiscal-rs

Bindings FFI: Uma Lib, Qualquer Linguagem

Estratégia de bindings FFI do fiscal-rs para Python, Node.js, WebAssembly e plataformas móveis

O fiscal-rs é escrito em Rust, mas foi projetado desde o início para ser consumido por qualquer linguagem. A estratégia de FFI (Foreign Function Interface) permite que um único núcleo de alta performance sirva aplicações em Python, Node.js, navegadores web e aplicações móveis nativas.

Por que Rust é ideal para FFI?

Rust possui características únicas que o tornam a melhor escolha para bibliotecas multi-linguagem:

CaracterísticaBenefício para FFI
Sem Garbage CollectorNão interfere com o GC da linguagem hospedeira (Python, JS, etc.)
Compatibilidade C ABIExporta funções extern "C" que qualquer linguagem pode chamar
Segurança de memóriaSem use-after-free, double-free ou data races — mesmo cruzando fronteiras FFI
Compilação para múltiplos alvosUm único codebase gera .so, .dylib, .dll, .wasm
Zero-cost abstractionsPerformance nativa sem overhead de runtime

Arquitetura: núcleo compartilhado

Todas as bindings compartilham o mesmo núcleo Rust. A lógica fiscal, cálculos de impostos e geração de XML existem em um único lugar — as bindings são camadas finas de tradução.

Loading diagram...

Bindings por linguagem

A binding Node.js já está disponível no npm. As demais (Python, WebAssembly e mobile) estão planejadas.

Python via PyO3 (planejada)

PyO3 é o framework padrão para criar extensões nativas de Python em Rust. Combinado com maturin, permite publicar no PyPI com pip install.

use pyo3::prelude::*;
use fiscal_core::types::*;
use fiscal_core::xml_builder::InvoiceBuilder;

/// Gera o XML de uma NF-e a partir dos dados fornecidos.
#[pyfunction]
fn build_invoice_xml(
    cnpj: &str,
    ie: &str,
    razao_social: &str,
    items: Vec<PyInvoiceItem>,
) -> PyResult<PyInvoiceResult> {
    let issuer = IssuerData::new(cnpj, ie, razao_social, /* ... */);
    let mut builder = InvoiceBuilder::new(
        issuer,
        SefazEnvironment::Homologation,
        InvoiceModel::NFe,
    );

    for item in items {
        builder = builder.add_item(item.into());
    }

    let invoice = builder.build()
        .map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(
            e.to_string()
        ))?;

    Ok(PyInvoiceResult {
        access_key: invoice.access_key().to_string(),
        xml: invoice.xml().to_string(),
    })
}

/// Modulo Python publicado como `fiscal_rs`.
#[pymodule]
fn fiscal_rs(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(build_invoice_xml, m)?)?;
    Ok(())
}

Uso em Python:

from fiscal_rs import build_invoice_xml

resultado = build_invoice_xml(
    cnpj="25028332000105",
    ie="140950881119",
    razao_social="Minha Empresa",
    items=[{
        "codigo": "001",
        "descricao": "Produto Teste",
        "ncm": "18069000",
        "cfop": "5102",
        "valor_total": 10000,  # R$ 100,00
    }],
)

print(f"Chave: {resultado.access_key}")
print(f"XML: {resultado.xml}")

Node.js via napi-rs

A binding Node.js já está publicada como @fiscal-rs/node no npm (npm install @fiscal-rs/node), com binários pré-compilados para Linux, macOS e Windows (x64 e arm64).

napi-rs gera addons nativos para Node.js com definições TypeScript automáticas. A performance é superior ao JavaScript puro para operações como geração de XML e cálculos fiscais.

use napi_derive::napi;
use fiscal_core::types::*;
use fiscal_core::xml_builder::InvoiceBuilder;

#[napi(object)]
pub struct InvoiceResult {
    pub access_key: String,
    pub xml: String,
}

#[napi]
pub fn build_invoice_xml(
    cnpj: String,
    ie: String,
    razao_social: String,
    items: Vec<JsInvoiceItem>,
) -> napi::Result<InvoiceResult> {
    let issuer = IssuerData::new(&cnpj, &ie, &razao_social, /* ... */);
    let mut builder = InvoiceBuilder::new(
        issuer,
        SefazEnvironment::Homologation,
        InvoiceModel::NFe,
    );

    for item in items {
        builder = builder.add_item(item.into());
    }

    let invoice = builder.build()
        .map_err(|e| napi::Error::from_reason(e.to_string()))?;

    Ok(InvoiceResult {
        access_key: invoice.access_key().to_string(),
        xml: invoice.xml().to_string(),
    })
}

Uso em TypeScript:

buildInvoice recebe um único objeto de configuração (campos em camelCase, equivalentes a InvoiceBuildData) e retorna { xml, accessKey }. Enums são strings em camelCase ('nfe', 'homologation', 'simplesNacional') e valores monetários são inteiros em centavos.

import { buildInvoice } from '@fiscal-rs/node';

const { xml, accessKey } = buildInvoice({
  model: 'nfe',
  environment: 'homologation',
  operationNature: 'VENDA',
  issuer: {
    taxId: '12345678000199',
    stateTaxId: '123456789',
    companyName: 'Minha Empresa',
    taxRegime: 'simplesNacional',
    stateCode: 'SP',
    cityCode: '3550308',
    cityName: 'Sao Paulo',
    street: 'Av Paulista',
    streetNumber: '1000',
    district: 'Bela Vista',
    zipCode: '01310100',
  },
  items: [{
    itemNumber: 1,
    productCode: '001',
    description: 'Produto Teste',
    ncm: '18069000',
    cfop: '5102',
    unitOfMeasure: 'UN',
    quantity: 1,
    unitPrice: 10000,   // centavos
    totalPrice: 10000,
    icmsCst: '102',
    icmsRate: 0,
    icmsAmount: 0,
    pisCst: '99',
    cofinsCst: '99',
  }],
});

console.log(`Chave: ${accessKey}`); // 44 dígitos
console.log(xml);

Para gerar e assinar em uma etapa, use buildAndSignInvoice(config, privateKey, certificate), que retorna { xml, signedXml, accessKey }.

WebAssembly via wasm-bindgen

wasm-bindgen compila o código Rust para WebAssembly, executável em qualquer navegador moderno. Ideal para aplicações web que precisam gerar XML ou calcular impostos no cliente.

A binding WASM expõe apenas os módulos que não dependem de I/O de rede ou sistema de arquivos: tipos, cálculos fiscais, geração de XML e QR Code. Assinatura digital e comunicação com SEFAZ exigem a binding nativa (Python ou Node.js).

import init, { buildInvoiceXml } from '@fiscal-rs/wasm';

await init(); // carrega o módulo .wasm

const resultado = buildInvoiceXml(/* ... */);

UniFFI para plataformas móveis (futuro)

UniFFI (Mozilla) gera bindings Kotlin e Swift a partir de uma definição de interface (UDL ou proc-macros). Permite usar o fiscal-rs em aplicações Android e iOS para PDV móvel.

// Kotlin (Android) — gerado automaticamente pelo UniFFI
val resultado = FiscalRs.buildInvoiceXml(
    cnpj = "25028332000105",
    ie = "140950881119",
    razaoSocial = "Minha Empresa",
    items = listOf(invoiceItem),
)

println("Chave: ${resultado.accessKey}")

Pipeline de build por alvo

Cada binding possui seu próprio pipeline de build e publicação:

Loading diagram...

Estrutura do workspace

As bindings ficam no diretório bindings/ do workspace:

fiscal-rs/
  crates/
    fiscal-core/        # lógica fiscal (zero I/O)
    fiscal-crypto/      # certificados e assinatura
    fiscal-sefaz/       # comunicação SEFAZ
  bindings/
    fiscal-py/          # PyO3 + maturin
      Cargo.toml
      pyproject.toml
    fiscal-node/        # napi-rs
      Cargo.toml
      package.json
    fiscal-wasm/        # wasm-bindgen + wasm-pack
      Cargo.toml

Comparação entre bindings

Python (PyO3)Node.js (napi-rs)WASMUniFFI
Instalaçãopip installnpm installnpm installMaven / CocoaPods
PerformanceNativaNativa~80% nativaNativa
Assinatura XMLSimSimNaoSim
SEFAZ (mTLS)SimSimNaoSim
Cálculos fiscaisSimSimSimSim
Geração XMLSimSimSimSim
QR CodeSimSimSimSim
Caso de usoBackend, scriptsBackend, APIFrontend webMobile (PDV)

A binding Node.js (@fiscal-rs/node) já está publicada; as demais (Python, WASM, mobile) estão planejadas. A API Rust nativa (fiscal-core, fiscal-crypto, fiscal-sefaz) segue sendo a interface de referência. Contribuições para as bindings são bem-vindas!

On this page