Toda aplicacao de software precisa de configuracao: enderecos de servidores, credenciais, parametros de execucao, limites e opcoes. Gerenciar configuracoes de forma robusta — suportando multiplos formatos de arquivo, variaveis de ambiente e valores padrao com validacao — e um problema que aparece em praticamente todo projeto. Neste walkthrough, vamos construir um gerenciador de configuracoes completo que carrega dados de arquivos TOML, JSON ou YAML, permite sobrescrever valores via variaveis de ambiente e valida que a configuracao final e consistente.
Este projeto e excelente para dominar o ecossistema serde em Rust, entender traits como Deserialize e Default, e aprender a projetar APIs ergonomicas que outros desenvolvedores podem usar como biblioteca.
O Que Vamos Construir
Nosso gerenciador de configuracoes tera os seguintes recursos:
- Carregamento de configuracao a partir de arquivos TOML, JSON ou YAML
- Deteccao automatica do formato pelo nome do arquivo
- Sobrescrita de valores via variaveis de ambiente
- Valores padrao para todos os campos
- Validacao com mensagens de erro descritivas
- Merge de multiplas fontes (arquivo + ambiente + padrao)
- Exibicao da configuracao final formatada
- CLI para inspecionar e validar arquivos de configuracao
Estrutura do Projeto
config-manager/
├── Cargo.toml
└── src/
├── main.rs
├── config.rs
├── carregador.rs
└── validador.rs
Configurando o Projeto
cargo new config-manager
cd config-manager
Configure o Cargo.toml:
[package]
name = "config-manager"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
serde_yaml = "0.9"
clap = { version = "4", features = ["derive"] }
colored = "2"
Usamos serde como framework central de serializacao, com backends para cada formato: toml para TOML, serde_json para JSON e serde_yaml para YAML. O clap fornece a interface de linha de comando e colored a saida formatada.
Passo 1: Definindo a Estrutura de Configuracao
O modulo config.rs define a estrutura da configuracao com valores padrao e suporte a serializacao em todos os formatos.
// src/config.rs
use serde::{Deserialize, Serialize};
/// Configuracao principal da aplicacao
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Configuracao {
/// Nome da aplicacao
#[serde(default = "padrao_nome_app")]
pub nome_app: String,
/// Ambiente de execucao (desenvolvimento, producao, teste)
#[serde(default = "padrao_ambiente")]
pub ambiente: String,
/// Configuracoes do servidor
#[serde(default)]
pub servidor: ConfigServidor,
/// Configuracoes do banco de dados
#[serde(default)]
pub banco_de_dados: ConfigBancoDeDados,
/// Configuracoes de log
#[serde(default)]
pub log: ConfigLog,
/// Configuracoes de seguranca
#[serde(default)]
pub seguranca: ConfigSeguranca,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigServidor {
/// Endereco de escuta
#[serde(default = "padrao_host")]
pub host: String,
/// Porta do servidor
#[serde(default = "padrao_porta")]
pub porta: u16,
/// Numero maximo de conexoes simultaneas
#[serde(default = "padrao_max_conexoes")]
pub max_conexoes: u32,
/// Timeout de requisicao em segundos
#[serde(default = "padrao_timeout")]
pub timeout_segundos: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigBancoDeDados {
/// URL de conexao com o banco
#[serde(default = "padrao_url_banco")]
pub url: String,
/// Tamanho maximo do pool de conexoes
#[serde(default = "padrao_pool_max")]
pub pool_maximo: u32,
/// Tamanho minimo do pool de conexoes
#[serde(default = "padrao_pool_min")]
pub pool_minimo: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigLog {
/// Nivel de log: trace, debug, info, warn, error
#[serde(default = "padrao_nivel_log")]
pub nivel: String,
/// Formato: texto ou json
#[serde(default = "padrao_formato_log")]
pub formato: String,
/// Caminho do arquivo de log (vazio para stdout)
#[serde(default)]
pub arquivo: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigSeguranca {
/// Chave secreta para tokens
#[serde(default = "padrao_chave_secreta")]
pub chave_secreta: String,
/// Tempo de expiracao do token em horas
#[serde(default = "padrao_expiracao_token")]
pub expiracao_token_horas: u32,
/// Origens permitidas para CORS
#[serde(default = "padrao_cors_origens")]
pub cors_origens: Vec<String>,
}
// Funcoes de valores padrao
fn padrao_nome_app() -> String { "minha-app".to_string() }
fn padrao_ambiente() -> String { "desenvolvimento".to_string() }
fn padrao_host() -> String { "127.0.0.1".to_string() }
fn padrao_porta() -> u16 { 8080 }
fn padrao_max_conexoes() -> u32 { 100 }
fn padrao_timeout() -> u64 { 30 }
fn padrao_url_banco() -> String { "sqlite://dados.db".to_string() }
fn padrao_pool_max() -> u32 { 10 }
fn padrao_pool_min() -> u32 { 2 }
fn padrao_nivel_log() -> String { "info".to_string() }
fn padrao_formato_log() -> String { "texto".to_string() }
fn padrao_chave_secreta() -> String { "ALTERAR_EM_PRODUCAO".to_string() }
fn padrao_expiracao_token() -> u32 { 24 }
fn padrao_cors_origens() -> Vec<String> { vec!["http://localhost:3000".to_string()] }
impl Default for Configuracao {
fn default() -> Self {
Self {
nome_app: padrao_nome_app(),
ambiente: padrao_ambiente(),
servidor: ConfigServidor::default(),
banco_de_dados: ConfigBancoDeDados::default(),
log: ConfigLog::default(),
seguranca: ConfigSeguranca::default(),
}
}
}
impl Default for ConfigServidor {
fn default() -> Self {
Self {
host: padrao_host(),
porta: padrao_porta(),
max_conexoes: padrao_max_conexoes(),
timeout_segundos: padrao_timeout(),
}
}
}
impl Default for ConfigBancoDeDados {
fn default() -> Self {
Self {
url: padrao_url_banco(),
pool_maximo: padrao_pool_max(),
pool_minimo: padrao_pool_min(),
}
}
}
impl Default for ConfigLog {
fn default() -> Self {
Self {
nivel: padrao_nivel_log(),
formato: padrao_formato_log(),
arquivo: String::new(),
}
}
}
impl Default for ConfigSeguranca {
fn default() -> Self {
Self {
chave_secreta: padrao_chave_secreta(),
expiracao_token_horas: padrao_expiracao_token(),
cors_origens: padrao_cors_origens(),
}
}
}
Cada campo usa #[serde(default = "funcao")] para definir um valor padrao quando o campo esta ausente no arquivo. Isso garante que a configuracao sempre tenha valores validos, mesmo que o arquivo contenha apenas uma parte dos campos.
Passo 2: Carregador Multi-Formato com Override de Ambiente
O modulo carregador.rs detecta o formato do arquivo, carrega a configuracao e aplica variaveis de ambiente como sobrescrita.
// src/carregador.rs
use crate::config::Configuracao;
use std::env;
use std::fs;
use std::path::Path;
/// Formato do arquivo de configuracao
#[derive(Debug, Clone, PartialEq)]
pub enum Formato {
Toml,
Json,
Yaml,
}
/// Erros possiveis durante o carregamento
#[derive(Debug)]
pub enum ErroCarregamento {
ArquivoNaoEncontrado(String),
LeituraFalhou(String),
FormatoDesconhecido(String),
ParseFalhou { formato: String, detalhe: String },
}
impl std::fmt::Display for ErroCarregamento {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ArquivoNaoEncontrado(c) => {
write!(f, "Arquivo nao encontrado: {}", c)
}
Self::LeituraFalhou(e) => write!(f, "Erro ao ler arquivo: {}", e),
Self::FormatoDesconhecido(ext) => {
write!(f, "Formato desconhecido: '{}'. Use .toml, .json ou .yaml", ext)
}
Self::ParseFalhou { formato, detalhe } => {
write!(f, "Erro ao processar {} : {}", formato, detalhe)
}
}
}
}
/// Detecta o formato do arquivo pela extensao
pub fn detectar_formato(caminho: &str) -> Result<Formato, ErroCarregamento> {
let extensao = Path::new(caminho)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
match extensao {
"toml" => Ok(Formato::Toml),
"json" => Ok(Formato::Json),
"yaml" | "yml" => Ok(Formato::Yaml),
outro => Err(ErroCarregamento::FormatoDesconhecido(outro.to_string())),
}
}
/// Carrega a configuracao de um arquivo, aplicando valores padrao
pub fn carregar_de_arquivo(caminho: &str) -> Result<Configuracao, ErroCarregamento> {
// Verifica se o arquivo existe
if !Path::new(caminho).exists() {
return Err(ErroCarregamento::ArquivoNaoEncontrado(caminho.to_string()));
}
// Le o conteudo do arquivo
let conteudo = fs::read_to_string(caminho)
.map_err(|e| ErroCarregamento::LeituraFalhou(e.to_string()))?;
// Detecta o formato e faz o parse
let formato = detectar_formato(caminho)?;
let config = parse_conteudo(&conteudo, &formato)?;
Ok(config)
}
/// Faz o parse do conteudo de acordo com o formato
fn parse_conteudo(
conteudo: &str,
formato: &Formato,
) -> Result<Configuracao, ErroCarregamento> {
match formato {
Formato::Toml => {
toml::from_str(conteudo).map_err(|e| ErroCarregamento::ParseFalhou {
formato: "TOML".to_string(),
detalhe: e.to_string(),
})
}
Formato::Json => {
serde_json::from_str(conteudo).map_err(|e| ErroCarregamento::ParseFalhou {
formato: "JSON".to_string(),
detalhe: e.to_string(),
})
}
Formato::Yaml => {
serde_yaml::from_str(conteudo).map_err(|e| ErroCarregamento::ParseFalhou {
formato: "YAML".to_string(),
detalhe: e.to_string(),
})
}
}
}
/// Aplica variaveis de ambiente como sobrescrita na configuracao.
/// Convencao: APP_SECAO_CAMPO (ex: APP_SERVIDOR_PORTA=9090)
pub fn aplicar_variaveis_ambiente(config: &mut Configuracao) {
// Servidor
if let Ok(val) = env::var("APP_NOME") {
config.nome_app = val;
}
if let Ok(val) = env::var("APP_AMBIENTE") {
config.ambiente = val;
}
if let Ok(val) = env::var("APP_SERVIDOR_HOST") {
config.servidor.host = val;
}
if let Ok(val) = env::var("APP_SERVIDOR_PORTA") {
if let Ok(porta) = val.parse::<u16>() {
config.servidor.porta = porta;
}
}
if let Ok(val) = env::var("APP_SERVIDOR_MAX_CONEXOES") {
if let Ok(n) = val.parse::<u32>() {
config.servidor.max_conexoes = n;
}
}
if let Ok(val) = env::var("APP_SERVIDOR_TIMEOUT") {
if let Ok(n) = val.parse::<u64>() {
config.servidor.timeout_segundos = n;
}
}
// Banco de dados
if let Ok(val) = env::var("APP_BANCO_URL") {
config.banco_de_dados.url = val;
}
if let Ok(val) = env::var("APP_BANCO_POOL_MAXIMO") {
if let Ok(n) = val.parse::<u32>() {
config.banco_de_dados.pool_maximo = n;
}
}
if let Ok(val) = env::var("APP_BANCO_POOL_MINIMO") {
if let Ok(n) = val.parse::<u32>() {
config.banco_de_dados.pool_minimo = n;
}
}
// Log
if let Ok(val) = env::var("APP_LOG_NIVEL") {
config.log.nivel = val;
}
if let Ok(val) = env::var("APP_LOG_FORMATO") {
config.log.formato = val;
}
if let Ok(val) = env::var("APP_LOG_ARQUIVO") {
config.log.arquivo = val;
}
// Seguranca
if let Ok(val) = env::var("APP_SEGURANCA_CHAVE_SECRETA") {
config.seguranca.chave_secreta = val;
}
if let Ok(val) = env::var("APP_SEGURANCA_EXPIRACAO_TOKEN") {
if let Ok(n) = val.parse::<u32>() {
config.seguranca.expiracao_token_horas = n;
}
}
if let Ok(val) = env::var("APP_SEGURANCA_CORS_ORIGENS") {
config.seguranca.cors_origens = val
.split(',')
.map(|s| s.trim().to_string())
.collect();
}
}
/// Converte a configuracao para o formato especificado
pub fn exportar(config: &Configuracao, formato: &Formato) -> Result<String, String> {
match formato {
Formato::Toml => {
toml::to_string_pretty(config).map_err(|e| e.to_string())
}
Formato::Json => {
serde_json::to_string_pretty(config).map_err(|e| e.to_string())
}
Formato::Yaml => {
serde_yaml::to_string(config).map_err(|e| e.to_string())
}
}
}
A funcao aplicar_variaveis_ambiente segue a convencao APP_SECAO_CAMPO — um padrao muito comum em aplicacoes de producao (ex: Docker, Kubernetes). Variaveis numericas sao convertidas com parse, e valores invalidos sao silenciosamente ignorados, mantendo o valor original.
Passo 3: Validacao da Configuracao
O modulo validador.rs verifica se a configuracao final e consistente e segura.
// src/validador.rs
use crate::config::Configuracao;
/// Resultado da validacao com lista de erros e avisos
pub struct ResultadoValidacao {
pub erros: Vec<String>,
pub avisos: Vec<String>,
}
impl ResultadoValidacao {
fn nova() -> Self {
Self {
erros: Vec::new(),
avisos: Vec::new(),
}
}
/// Retorna true se nao ha erros (avisos sao permitidos)
pub fn eh_valido(&self) -> bool {
self.erros.is_empty()
}
}
/// Valida a configuracao e retorna erros e avisos
pub fn validar(config: &Configuracao) -> ResultadoValidacao {
let mut resultado = ResultadoValidacao::nova();
// Validar nome da aplicacao
if config.nome_app.trim().is_empty() {
resultado.erros.push(
"O nome da aplicacao nao pode ser vazio.".to_string(),
);
}
// Validar ambiente
let ambientes_validos = ["desenvolvimento", "producao", "teste", "homologacao"];
if !ambientes_validos.contains(&config.ambiente.as_str()) {
resultado.erros.push(format!(
"Ambiente '{}' invalido. Use: {}",
config.ambiente,
ambientes_validos.join(", ")
));
}
// Validar servidor
if config.servidor.porta == 0 {
resultado.erros.push(
"A porta do servidor nao pode ser 0.".to_string(),
);
}
if config.servidor.porta < 1024 && config.servidor.porta != 0 {
resultado.avisos.push(format!(
"Porta {} requer privilegios de root.",
config.servidor.porta
));
}
if config.servidor.max_conexoes == 0 {
resultado.erros.push(
"O numero maximo de conexoes deve ser maior que 0.".to_string(),
);
}
if config.servidor.timeout_segundos == 0 {
resultado.avisos.push(
"Timeout de 0 segundos desabilita o timeout.".to_string(),
);
}
// Validar banco de dados
if config.banco_de_dados.url.trim().is_empty() {
resultado.erros.push(
"A URL do banco de dados nao pode ser vazia.".to_string(),
);
}
if config.banco_de_dados.pool_minimo > config.banco_de_dados.pool_maximo {
resultado.erros.push(format!(
"Pool minimo ({}) nao pode ser maior que o maximo ({}).",
config.banco_de_dados.pool_minimo, config.banco_de_dados.pool_maximo
));
}
// Validar log
let niveis_validos = ["trace", "debug", "info", "warn", "error"];
if !niveis_validos.contains(&config.log.nivel.as_str()) {
resultado.erros.push(format!(
"Nivel de log '{}' invalido. Use: {}",
config.log.nivel,
niveis_validos.join(", ")
));
}
let formatos_validos = ["texto", "json"];
if !formatos_validos.contains(&config.log.formato.as_str()) {
resultado.erros.push(format!(
"Formato de log '{}' invalido. Use: {}",
config.log.formato,
formatos_validos.join(", ")
));
}
// Validar seguranca
if config.ambiente == "producao"
&& config.seguranca.chave_secreta == "ALTERAR_EM_PRODUCAO"
{
resultado.erros.push(
"Chave secreta padrao detectada em ambiente de producao! Defina uma chave segura.".to_string(),
);
}
if config.seguranca.chave_secreta.len() < 16 {
resultado.avisos.push(
"A chave secreta tem menos de 16 caracteres. Considere usar uma chave mais longa.".to_string(),
);
}
if config.seguranca.expiracao_token_horas == 0 {
resultado.erros.push(
"O tempo de expiracao do token deve ser maior que 0.".to_string(),
);
}
if config.seguranca.cors_origens.is_empty() {
resultado.avisos.push(
"Nenhuma origem CORS configurada. Requisicoes cross-origin serao bloqueadas.".to_string(),
);
}
resultado
}
A validacao distingue entre erros (problemas que impedem a execucao) e avisos (situacoes que merecem atencao mas nao sao fatais). Isso e especialmente util para detectar configuracoes inseguras em ambiente de producao.
Passo 4: Juntando Tudo no main.rs
// src/main.rs
mod carregador;
mod config;
mod validador;
use carregador::Formato;
use clap::{Parser, Subcommand};
use colored::*;
use config::Configuracao;
#[derive(Parser)]
#[command(name = "config-manager")]
#[command(about = "Gerenciador de configuracoes multi-formato")]
struct Cli {
#[command(subcommand)]
comando: Comando,
}
#[derive(Subcommand)]
enum Comando {
/// Carrega e exibe a configuracao de um arquivo
Carregar {
/// Caminho do arquivo de configuracao
arquivo: String,
/// Aplicar variaveis de ambiente como sobrescrita
#[arg(short, long)]
ambiente: bool,
},
/// Valida um arquivo de configuracao
Validar {
/// Caminho do arquivo de configuracao
arquivo: String,
},
/// Gera um arquivo de configuracao com valores padrao
Gerar {
/// Formato de saida: toml, json ou yaml
#[arg(short, long, default_value = "toml")]
formato: String,
/// Caminho do arquivo de saida (padrao: stdout)
#[arg(short, long)]
saida: Option<String>,
},
/// Converte um arquivo de configuracao para outro formato
Converter {
/// Arquivo de entrada
entrada: String,
/// Formato de saida: toml, json ou yaml
#[arg(short, long)]
formato: String,
},
}
fn main() {
let cli = Cli::parse();
match cli.comando {
Comando::Carregar { arquivo, ambiente } => {
cmd_carregar(&arquivo, ambiente);
}
Comando::Validar { arquivo } => {
cmd_validar(&arquivo);
}
Comando::Gerar { formato, saida } => {
cmd_gerar(&formato, saida.as_deref());
}
Comando::Converter { entrada, formato } => {
cmd_converter(&entrada, &formato);
}
}
}
fn cmd_carregar(arquivo: &str, aplicar_env: bool) {
println!(
"{} Carregando configuracao de '{}'...",
"INFO:".blue().bold(),
arquivo
);
let mut config = match carregador::carregar_de_arquivo(arquivo) {
Ok(c) => c,
Err(e) => {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
};
if aplicar_env {
println!(
"{} Aplicando variaveis de ambiente...",
"INFO:".blue().bold()
);
carregador::aplicar_variaveis_ambiente(&mut config);
}
exibir_config(&config);
}
fn cmd_validar(arquivo: &str) {
let config = match carregador::carregar_de_arquivo(arquivo) {
Ok(c) => c,
Err(e) => {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
};
let resultado = validador::validar(&config);
if !resultado.avisos.is_empty() {
println!("{}", "Avisos:".yellow().bold());
for aviso in &resultado.avisos {
println!(" {} {}", "AVISO:".yellow(), aviso);
}
println!();
}
if !resultado.erros.is_empty() {
println!("{}", "Erros:".red().bold());
for erro in &resultado.erros {
println!(" {} {}", "ERRO:".red(), erro);
}
std::process::exit(1);
}
println!(
"{} Configuracao valida! ({} aviso(s))",
"OK".green().bold(),
resultado.avisos.len()
);
}
fn cmd_gerar(formato_str: &str, saida: Option<&str>) {
let formato = match formato_str {
"toml" => Formato::Toml,
"json" => Formato::Json,
"yaml" | "yml" => Formato::Yaml,
_ => {
eprintln!(
"{} Formato '{}' nao suportado. Use: toml, json, yaml",
"ERRO:".red().bold(),
formato_str
);
std::process::exit(1);
}
};
let config = Configuracao::default();
let conteudo = match carregador::exportar(&config, &formato) {
Ok(c) => c,
Err(e) => {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
};
match saida {
Some(caminho) => {
if let Err(e) = std::fs::write(caminho, &conteudo) {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
println!(
"{} Configuracao gerada em '{}'",
"OK".green().bold(),
caminho
);
}
None => {
println!("{}", conteudo);
}
}
}
fn cmd_converter(entrada: &str, formato_str: &str) {
let formato_saida = match formato_str {
"toml" => Formato::Toml,
"json" => Formato::Json,
"yaml" | "yml" => Formato::Yaml,
_ => {
eprintln!(
"{} Formato '{}' nao suportado. Use: toml, json, yaml",
"ERRO:".red().bold(),
formato_str
);
std::process::exit(1);
}
};
let config = match carregador::carregar_de_arquivo(entrada) {
Ok(c) => c,
Err(e) => {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
};
let conteudo = match carregador::exportar(&config, &formato_saida) {
Ok(c) => c,
Err(e) => {
eprintln!("{} {}", "ERRO:".red().bold(), e);
std::process::exit(1);
}
};
println!("{}", conteudo);
}
fn exibir_config(config: &Configuracao) {
println!("\n{}", "=== Configuracao Carregada ===".bold().cyan());
println!(" Nome: {}", config.nome_app);
println!(" Ambiente: {}", config.ambiente);
println!();
println!("{}", " [servidor]".bold());
println!(" host: {}", config.servidor.host);
println!(" porta: {}", config.servidor.porta);
println!(" max_conexoes: {}", config.servidor.max_conexoes);
println!(" timeout: {}s", config.servidor.timeout_segundos);
println!();
println!("{}", " [banco_de_dados]".bold());
println!(" url: {}", config.banco_de_dados.url);
println!(" pool_maximo: {}", config.banco_de_dados.pool_maximo);
println!(" pool_minimo: {}", config.banco_de_dados.pool_minimo);
println!();
println!("{}", " [log]".bold());
println!(" nivel: {}", config.log.nivel);
println!(" formato: {}", config.log.formato);
println!(
" arquivo: {}",
if config.log.arquivo.is_empty() {
"(stdout)"
} else {
&config.log.arquivo
}
);
println!();
println!("{}", " [seguranca]".bold());
println!(" chave_secreta: {}...", &config.seguranca.chave_secreta[..config.seguranca.chave_secreta.len().min(8)]);
println!(" expiracao_token: {}h", config.seguranca.expiracao_token_horas);
println!(
" cors_origens: {}",
config.seguranca.cors_origens.join(", ")
);
}
Como Executar
cargo build --release
Primeiro, gere um arquivo de configuracao de exemplo:
# Gerar configuracao padrao em TOML
./target/release/config-manager gerar --formato toml --saida config.toml
# Gerar em JSON
./target/release/config-manager gerar --formato json --saida config.json
# Gerar em YAML
./target/release/config-manager gerar --formato yaml --saida config.yaml
Exemplos de uso:
# Carregar e exibir configuracao
./target/release/config-manager carregar config.toml
# === Configuracao Carregada ===
# Nome: minha-app
# Ambiente: desenvolvimento
# [servidor]
# host: 127.0.0.1
# porta: 8080
# ...
# Carregar com sobrescrita de variaveis de ambiente
APP_SERVIDOR_PORTA=9090 APP_AMBIENTE=producao \
./target/release/config-manager carregar config.toml --ambiente
# Validar configuracao
./target/release/config-manager validar config.toml
# OK Configuracao valida! (1 aviso(s))
# Converter de TOML para JSON
./target/release/config-manager converter config.toml --formato json
# Converter de JSON para YAML
./target/release/config-manager converter config.json --formato yaml
Desafios para Expandir
Suporte a profiles: Implemente configuracoes por profile (ex:
config.producao.toml,config.teste.toml) que herdam do arquivo base e sobrescrevem apenas os campos especificos do ambiente.Hot reload: Adicione a capacidade de monitorar o arquivo de configuracao com
notifye recarregar automaticamente quando ele for alterado, notificando a aplicacao via canal.Criptografia de segredos: Implemente criptografia de campos sensiveis (chaves, senhas) no arquivo de configuracao usando a crate
aes-gcm, com um comandoencryptedecryptna CLI.Validacao com schema: Crie um sistema de schema que descreva os tipos esperados, intervalos validos e dependencias entre campos, permitindo validacao generica de qualquer estrutura.
Merge de multiplos arquivos: Permita carregar e combinar configuracoes de varios arquivos em cascata (ex:
base.toml+local.toml), onde arquivos posteriores sobrescrevem valores dos anteriores.
Veja Tambem
- HashMap: Tabelas Hash — estrutura usada internamente pelo serde para mapas
- Modulo env — acesso a variaveis de ambiente em Rust
- Tipo Result — tratamento de erros no carregamento de configuracao
- Lendo Arquivos TOML — receita basica de leitura TOML
- Variaveis de Ambiente — trabalhando com env vars em Rust