diff options
Diffstat (limited to 'internal/bot')
| -rw-r--r-- | internal/bot/bot.go | 8 | ||||
| -rw-r--r-- | internal/bot/format.go | 33 | ||||
| -rw-r--r-- | internal/bot/format_test.go | 29 |
3 files changed, 45 insertions, 25 deletions
diff --git a/internal/bot/bot.go b/internal/bot/bot.go index 4abf02c..b361841 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -13,15 +13,13 @@ import ( cepis "cep.is/go" - "cep.is/telegrambot/internal/lookup" "cep.is/telegrambot/internal/telegram" ) // Lookups is the subset of the cep.is client this bot needs. Depending on the // interface rather than the concrete client keeps the seam narrow and visible. // -// A failure may be a [*lookup.NotFoundError], which still matches -// cepis.ErrCEPNotFound and additionally names the municipality the CEP would +// A failure may be a [*cepis.Error] carrying the municipality the CEP would // belong to; nothing here has to know that, because [ErrorMessage] reads it. type Lookups interface { Lookup(ctx context.Context, cep string) (*cepis.Address, error) @@ -273,8 +271,8 @@ func inlineErrorTitle(err error) string { // Titles and descriptions are plain text for Telegram, so nothing here is // escaped: HTML applies to the message body alone. func inlineErrorDescription(err error) string { - if faixa := lookup.FaixaOf(err); faixa != nil { - return "Cairia em " + faixa.Place() + " — toque para ver" + if faixa := faixaOf(err); faixa != nil { + return "Cairia em " + place(faixa) + " — toque para ver" } return "Toque para enviar o motivo" } diff --git a/internal/bot/format.go b/internal/bot/format.go index b94dee4..86a0ac9 100644 --- a/internal/bot/format.go +++ b/internal/bot/format.go @@ -9,8 +9,6 @@ import ( "time" cepis "cep.is/go" - - "cep.is/telegrambot/internal/lookup" ) // ParseMode is the formatting Telegram applies to every message this bot sends. @@ -197,14 +195,14 @@ const ( // CEPs are spread over nearly five thousand numbers — so its ends are the // extremes of what exists, not a list of what is valid, and the reply says so. func notFoundMessage(err error) string { - faixa := lookup.FaixaOf(err) + faixa := faixaOf(err) if faixa == nil || faixa.Localidade == "" { return notFoundHead + "\n" + notFoundAdvice } var b strings.Builder b.WriteString(notFoundHead + "\n") - b.WriteString("Ele cairia em <b>" + esc(faixa.Place()) + "</b>") + b.WriteString("Ele cairia em <b>" + esc(place(faixa)) + "</b>") count := faixaCount(faixa) span := faixaSpan(faixa) @@ -225,10 +223,33 @@ func notFoundMessage(err error) string { return b.String() } +// faixaOf returns the municipality the API named on a 404, or nil when it named +// none. Any error is safe to pass, including nil: only a *cepis.Error carrying +// a faixa yields one. +func faixaOf(err error) *cepis.Faixa { + var cerr *cepis.Error + if errors.As(err, &cerr) { + return cerr.Faixa + } + return nil +} + +// place is the municipality as it is written to a user: "Areal/RJ", or just +// "Areal" when the API sent no UF. +func place(faixa *cepis.Faixa) string { + if faixa == nil { + return "" + } + if faixa.UF == "" { + return faixa.Localidade + } + return faixa.Localidade + "/" + faixa.UF +} + // faixaCount renders how many CEPs the municipality has, or "" when the API did // not say. Without it the two ends of the range read as a count of one big // block, which is the misreading the number exists to prevent. -func faixaCount(faixa *lookup.Faixa) string { +func faixaCount(faixa *cepis.Faixa) string { switch { case faixa.CEPs <= 0: return "" @@ -241,7 +262,7 @@ func faixaCount(faixa *lookup.Faixa) string { // faixaSpan renders the ends of the range, or "" when they are missing or are // the same number — "de X a X" is not a range, and printing a single CEP there // would hand the user a guess to try. -func faixaSpan(faixa *lookup.Faixa) string { +func faixaSpan(faixa *cepis.Faixa) string { first, last := maskCEP(faixa.CEPIni), maskCEP(faixa.CEPFim) if first == "" || last == "" || first == last { return "" diff --git a/internal/bot/format_test.go b/internal/bot/format_test.go index 513db36..8e10b3e 100644 --- a/internal/bot/format_test.go +++ b/internal/bot/format_test.go @@ -7,8 +7,6 @@ import ( "testing" cepis "cep.is/go" - - "cep.is/telegrambot/internal/lookup" ) func decodeAddress(t *testing.T, body string) *cepis.Address { @@ -118,11 +116,14 @@ func TestErrorMessagesAreDistinct(t *testing.T) { } } -func notFound(faixa *lookup.Faixa) error { - return &lookup.NotFoundError{Faixa: faixa, Err: cepis.ErrCEPNotFound} +// notFound builds the error the client library returns for a CEP absent from +// the DNE, with the municipality the API named on the 404 — or without one, +// for a CEP in a band no municipality owns. +func notFound(faixa *cepis.Faixa) error { + return &cepis.Error{Kind: cepis.ErrCEPNotFound, Faixa: faixa} } -var areal = &lookup.Faixa{ +var areal = &cepis.Faixa{ Localidade: "Areal", UF: "RJ", Estado: "Rio de Janeiro", Regiao: "Sudeste", IBGE: "3300225", CEPIni: "25845001", CEPFim: "25849899", CEPs: 118, } @@ -168,7 +169,7 @@ func TestNotFoundWithoutFaixaIsUnchanged(t *testing.T) { for name, err := range map[string]error{ "plain sentinel": cepis.ErrCEPNotFound, "no faixa": notFound(nil), - "empty faixa": notFound(&lookup.Faixa{}), + "empty faixa": notFound(&cepis.Faixa{}), } { if got := ErrorMessage(err); got != want { t.Fatalf("%s:\n got %q\nwant %q", name, got, want) @@ -177,7 +178,7 @@ func TestNotFoundWithoutFaixaIsUnchanged(t *testing.T) { } func TestNotFoundEscapesTheMunicipality(t *testing.T) { - text := ErrorMessage(notFound(&lookup.Faixa{ + text := ErrorMessage(notFound(&cepis.Faixa{ Localidade: "Santa <b>Rita</b> & Cia", UF: "M&G", CEPIni: "25845001", CEPFim: "25849899", CEPs: 2, })) @@ -194,32 +195,32 @@ func TestNotFoundEscapesTheMunicipality(t *testing.T) { // take the sentence down with it. func TestNotFoundDegradesFieldByField(t *testing.T) { cases := map[string]struct { - faixa *lookup.Faixa + faixa *cepis.Faixa present []string absent []string }{ "no count": { - faixa: &lookup.Faixa{Localidade: "Areal", UF: "RJ", CEPIni: "25845001", CEPFim: "25849899"}, + faixa: &cepis.Faixa{Localidade: "Areal", UF: "RJ", CEPIni: "25845001", CEPFim: "25849899"}, present: []string{"Areal/RJ", "25845-001", "25849-899", "esparsa"}, absent: []string{"CEPs,"}, }, "no range": { - faixa: &lookup.Faixa{Localidade: "Areal", UF: "RJ", CEPs: 118}, + faixa: &cepis.Faixa{Localidade: "Areal", UF: "RJ", CEPs: 118}, present: []string{"Areal/RJ", "118 CEPs"}, absent: []string{"<code>", "esparsa"}, }, "only the name": { - faixa: &lookup.Faixa{Localidade: "Areal"}, + faixa: &cepis.Faixa{Localidade: "Areal"}, present: []string{"cairia em <b>Areal</b>."}, absent: []string{"<code>", "esparsa", "Areal/"}, }, "single CEP": { - faixa: &lookup.Faixa{Localidade: "Vila", UF: "SP", CEPIni: "12345678", CEPFim: "12345678", CEPs: 1}, + faixa: &cepis.Faixa{Localidade: "Vila", UF: "SP", CEPIni: "12345678", CEPFim: "12345678", CEPs: 1}, present: []string{"1 CEP."}, absent: []string{"12345-678"}, }, "unusable range": { - faixa: &lookup.Faixa{Localidade: "Areal", UF: "RJ", CEPIni: "abc", CEPFim: "2584", CEPs: 118}, + faixa: &cepis.Faixa{Localidade: "Areal", UF: "RJ", CEPIni: "abc", CEPFim: "2584", CEPs: 118}, present: []string{"118 CEPs."}, absent: []string{"abc", "2584"}, }, @@ -251,7 +252,7 @@ func TestInlineErrorDescriptionNamesTheMunicipality(t *testing.T) { } // Titles and descriptions are plain text: escaping them would show the // entities to the user. - got := inlineErrorDescription(notFound(&lookup.Faixa{Localidade: "Santa & Cia", UF: "SP"})) + got := inlineErrorDescription(notFound(&cepis.Faixa{Localidade: "Santa & Cia", UF: "SP"})) if !strings.Contains(got, "Santa & Cia/SP") { t.Fatalf("description = %q", got) } |