Result em vez de exceções?Exceções são para o inesperado. Um pedido inexistente ou um e-mail duplicado não é inesperado — é um desfecho que quem chamou precisa tratar. Devolvê-lo faz com que a assinatura seja honesta (Result<Order> Get(string id) admite falha; Order Get(string id) não), várias falhas possam ser reportadas de uma vez, e o mapeamento de transporte seja dado em vez de uma cadeia de blocos catch.
ErrorKind.Unexpected continua existindo para falhas genuínas, e recebe tratamento especial.
T para Result<T>?Para que um valor nunca vire sucesso por acidente. Com conversão implícita, mudar o tipo de retorno de um método para Result<T> compila em silêncio e todo return value; existente continua funcionando — inclusive aqueles que agora deveriam ser falhas. Construção explícita transforma isso em um erro de compilação que você é obrigado a olhar.
ErrorKind?Porque um conjunto fechado de kinds torna o mapeamento HTTP total. Todo erro que o domínio pode produzir já tem um status definido — sem registro para manter sincronizado, sem branch default para esquecer, sem um 500 porque alguém adicionou um kind e passou batido em um switch.
A especificidade vive no espaço de códigos, que é aberto:
Error.Custom("order.already_shipped", ErrorKind.Conflict, new { orderId });
Clientes decidem pelo código. O kind só define o status e o rank de severidade.
Para que uma falha genuína nunca seja mascarada por uma mensagem de validação que por acaso foi listada primeiro. Um resultado carregando um erro de Validation e um de Unexpected é um 500, não um 400.
Nada se perde: todos os erros são enviados no array errors de qualquer forma. Empates dentro de um rank — Unauthorized e Forbidden, ou Validation e BadRequest — vão para o primeiro erro do resultado.
201 Created?Faça o branch antes de converter. ToHttpResult cuida do caminho de falha; você mantém o de sucesso:
var result = handler.Handle(cmd);
return result.IsSuccess
? Results.Created($"/orders/{result.Value.Id}", result.Value)
: result.ToHttpResult(http);
AddOffside lança na inicialização. Por quê?Você não registrou um catálogo de cultura invariante:
options.AddJson(CultureInfo.InvariantCulture, File.ReadAllText("errors/errors.json"));
Ele é o fallback final de toda busca, então é obrigatório. Falhar no boot é deliberado — a alternativa é descobrir na primeira requisição com erro em produção.
not_foundO resolver não achou um template e devolveu o código. Verifique se:
<None Update="errors\*.json" CopyToOutputDirectory="PreserveNewest" />).AddJson, não o caminho.{token} aparece literalmente na mensagemO template referenciou um argumento que o erro não carrega, ou carrega como nulo. Error.NotFound("order") contra "{resource} '{id}' não foi encontrado." produz order '{id}' não foi encontrado. — o argumento id é nulo e é pulado, não zerado. Passe o argumento, ou remova o token do template.
DomainException?Só onde a assinatura está fora do seu controle — um construtor, ou uma interface que você não escreveu:
throw Error.Validation("quantity", attemptedValue: quantity).ToException();
Se metade das falhas de uma base lança, a garantia de que a assinatura diz o que pode dar errado se perde, e com ela a maior parte da razão de usar esta biblioteca.
Copie o catálogo, traduza os valores, registre. Traduções parciais são aceitáveis — o que faltar cai para a cultura pai e depois para o catálogo invariante. Veja Mensagens e culturas.
Do header Accept-Language da requisição — primeiro range, sem quality values — a menos que você passe uma explicitamente. Um valor ausente, vazio, * ou não reconhecido cai para CultureInfo.CurrentUICulture. Um header malformado nunca derruba uma requisição.
Sim. Offside tem como alvo netstandard2.0 e não depende de ASP.NET. Workers, CLIs e class libraries podem devolver Result e resolver mensagens com IErrorMessageResolver; só o mapeamento HTTP vive em Offside.AspNetCore.
ToActionResult(result, resolver, exposeExceptionDetails) para o Result unitário?Um descuido no conjunto de sobrecargas, mantido em vez de alterado enquanto a biblioteca é pré-1.0. O Result<T> genérico tem. Para um Result unitário, passe uma cultura explicitamente ou passe null pela sobrecarga com options para cair no Accept-Language.
Error.Arguments?Argumentos alimentam templates de mensagem, e mensagens vão para clientes. Identificadores e nomes de campo tudo bem; tokens, hashes de senha e connection strings não. Material de diagnóstico pertence a Error.Unexpected(detail), que é sanitizado antes de sair na resposta.
DomainErrors?Histórico — o projeto foi renomeado para Offside. A solution, os pacotes e os namespaces são todos Offside; só um clone local pode ainda carregar o nome antigo.
Está pré-1.0. Releases minor podem incluir mudanças que quebram; veja o changelog. O comportamento documentado aqui é coberto por testes em net8.0 e net10.0.