Rule Cascade em Go
Módulo: go get rulescascade.com/go (Go 1.26+, sem dependências além da biblioteca padrão e
gopkg.in/yaml.v3). Os bundles vêm de rcas compile --all para build/rules/. Nunca avalie o YAML
em tempo de execução e nunca copie uma regra para o código.
Carregue uma vez, avalie por requisição
import rulecascade "rulescascade.com/go"
// Na inicialização. Qualquer erro: não iniciar.
data, err := os.ReadFile("build/rules/<ruleset-id>.bundle.json")
doc, err := rulecascade.ParseJSON(data)
regras, err := rulecascade.FromBundle(doc)
Prefira o middleware (a partir de 1.0.0-alpha.7): rulescascade.com/go/rulehttp constrói a requisição
a partir do corpo JSON (que permanece legível), aplica as regras e responde uma requisição negada com
os detalhes do problema 422 abaixo, o mesmo em todo runtime do Rule Cascade.
mux.Handle("POST /orders", rulehttp.Enforce(regras, rulehttp.Options{Entity: "Order", Operation: "create",
Actor: func(r *http.Request) map[string]any { return actorFromToken(r) }})(createOrder))
// no handler: rulehttp.FromContext(r.Context()) é o resultado permitido
Em outros lugares, regras.Enforce(requisicao, "servidor", nil) retorna um erro *rulecascade.RuleViolation
(Problem() é o corpo) e rulehttp.WriteError(w, err) responde. Por baixo,
regras.Evaluate(requisicao, "servidor", nil) retorna um *rulecascade.Result (Decision, Findings,
Effects, Commands; result.Allowed()). Um *RuleSet é seguro para uso concorrente. Todo
handler de estado mutável segue quatro passos:
- Avalie no servidor com
map[string]any{"entity", "operation", "data", "original", "actor", "resolutions"}. O actor vem do middleware de autenticação, nunca de um cabeçalho controlado pelo chamador. Decodifique corpos de requisição comrulecascade.ParseJSON(oujson.Decoder.UseNumber()) para que números permaneçam exatos. - Recuse em caso de negação com 422
application/problem+json(type: urn:rule-cascade:rule-violation) carregando o resultado; para gRPC,codes.FailedPreconditioncom os achados nos detalhes. - Persista, após aplicar efeitos
compute(Type == "value"): o valor do servidor é armazenado. - Execute
result.Commandsapós o commit, uma vez porIdempotencyKey(uma tabela de saída, ou um broker com deduplicação).
Posicionamento: uma função por entidade que constrói a requisição e avalia, chamada a partir de todo handler
(net/http, chi, gin, echo da mesma forma). rulecascade.NewHolder recarrega bundles em um intervalo ou um
agendamento cron e mantém as últimas regras boas.
Substituindo validação existente
rcas analyze --module <module> lista tags de struct validate: e binding:, ozzo-validation, motores
de regras, verificações manuais errors.New, limites nomeados (const QuantidadeMaxima = 10), comparações com
números e ramos switch. Um candidato com drift significa que outra linguagem do projeto (frequentemente o front end) decide o mesmo nome com outro valor: um regra precisa decidir ambos. Mantenha
verificações de forma na decodificação; mova decisões de negócio para regras. Adicione um teste de paridade orientado a tabela sobre as mesmas
entradas para a verificação antiga e o motor, depois remova a verificação antiga.
Testes
Testes orientados a tabela sobre Decision e códigos Code de achados, não textos de mensagem. Testes golden no
conjunto de regras (rcas check) testam as regras; CI executa rcas check e rcas compile --all antes de go test.
Referência: https://rulescascade.com/usage/go/