Retorne dados reais do catálogo

GolangBeginner
Pratique Agora

Introdução

Um balcão comunitário tem uma pequena coleção de objetos perdidos. Sua página preparada pode exibir itens, mas a API de exemplo retorna apenas um registro. Você conectará registros Go reais às respostas de lista e detalhes para que os visitantes encontrem o local correto de retirada.

Você já deve conhecer funções, estruturas, slices, maps e módulos Go, além de caminhos básicos no terminal. HTTP, JSON e testes de handlers são apresentados aqui. O ambiente no navegador inclui Go e uma página pronta. Esta unidade começa de forma independente; não precisa de arquivos de outro laboratório. Reserve cerca de 55 minutos, incluindo leitura e erros comuns; esta é uma estimativa do autor.

Selecione um item sem prejudicar a lista

Nesta etapa, preserve uma coleção funcional de um cartão enquanto acrescenta detalhes reais dos itens. Comece na aba Workspace. Seu shell entra em /home/labex/project/lost-found; o seguinte cd restaura esse local se você voltar depois.

Go está instalado em /usr/local/go/bin. export adiciona esse diretório ao caminho de busca de comandos deste shell, mantendo o $PATH existente. Isso vale para a sessão atual do terminal; repita em um novo shell. Nenhuma configuração global do shell é alterada.

cd /home/labex/project/lost-found
export PATH=/usr/local/go/bin:$PATH

curl envia uma requisição. -i inclui os cabeçalhos da resposta, e as aspas simples mantêm a URL inteira. GET pede a leitura de um recurso. /api/items identifica a coleção; 127.0.0.1:8080 é o serviço nesta mesma máquina. Primeiro use o exemplo normal:

curl -i 'http://127.0.0.1:8080/api/items'

Espere 200 OK, um cabeçalho Content-Type: application/json e um array contendo LF-101, o guarda-chuva azul na biblioteca. O status HTTP descreve o resultado da requisição; o corpo contém os dados. JSON usa colchetes para arrays e chaves para objetos. Um 200 sozinho não prova que a coleção está completa. Mude para Lost and Found e veja seu único cartão; depois volte para Workspace.

cat exibe um arquivo sem editá-lo. Leia os registros de origem, a estrutura de entrada e seu pequeno handler inicial:

cat items.json
cat main.go
cat handler.go

Há seis registros. Item agrupa ID, Name, Category e Location; campos exportados com inicial maiúscula podem ser codificados. Tags como `json:"id"` escolhem chaves JSON em minúsculas. main.go carrega a coleção, fornece a página preparada e passa seu slice de itens para catalogHandler. Preserve main.go, go.mod, items.json, serve.py e web/; edite apenas handler.go e quaisquer arquivos Go ou testes adicionais.

Antes de alterar o código, preveja a diferença entre uma requisição para LF-101 e outra para LF-103. Ambos os nomes são Blue umbrella, mas os locais são Library desk e Cafe counter. Qual campo pode selecionar apenas um registro? Agora solicite o segundo ID:

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

O código inicial ainda retorna seu array do primeiro registro. Essa é uma resposta recebida, mas não um detalhe correto. Sua tarefa é retornar o objeto selecionado pelo ID e manter disponível a resposta atual da lista. Não resolva isso retornando o primeiro objeto para todos os caminhos.

Um handler é código Go chamado a cada requisição. r *http.Request aponta para a requisição recebida. w http.ResponseWriter é o destino: Header define cabeçalhos e as escritas enviam o corpo. http.Handler descreve esse comportamento; http.HandlerFunc adapta uma função a ele. A função anônima retornada lembra os items fornecidos. Você não precisa escrever um analisador HTTP.

Antes de editar, salve uma cópia. cp copia o arquivo; a extensão .backup evita deliberadamente .go, que criaria definições duplicadas quando Go compilasse o pacote.

cp handler.go handler.go.backup

Abra o arquivo real no editor de terminal. O LC_ALL=C.UTF-8 restrito ao comando faz nano interpretar o texto como UTF-8, incluindo caracteres acentuados. Ele afeta apenas essa execução do editor, não as configurações globais do shell:

LC_ALL=C.UTF-8 nano handler.go

No nano, Ctrl significa manter Control pressionado enquanto pressiona a tecla indicada. Use as setas para mover-se. Pressione Ctrl+W, digite catalogHandler e depois Enter para encontrar a função; Ctrl+C cancela uma busca inacabada. Edite digitando normalmente, com Backspace e Delete. Ctrl+K recorta a linha atual; use-o apenas em uma linha que pretende substituir. Os atalhos na parte inferior usam ^ para indicar Control. Você está editando o handler existente, não criando um segundo programa.

Implemente as seguintes decisões dentro da função retornada, depois do cabeçalho de tipo de conteúdo existente. Trabalhe em um ramo por vez:

Primeiro, leia r.URL.Path. Se for igual a /api/items, preserve a codificação do array items[:1] inicial e retorne imediatamente. Isso mantém a lista normal de um cartão enquanto você trabalha nos detalhes.

Depois, caso contrário, reconheça o prefixo /api/items/. Adicione a importação strings da biblioteca padrão ao lado das importações existentes. strings.HasPrefix(path, prefix) verifica um prefixo, e strings.TrimPrefix(path, prefix) o remove. Armazene o ID restante em uma variável local.

Em seguida, percorra o slice fornecido com for _, item := range items. Compare item.ID com o ID solicitado, não com o nome. Para a correspondência, codifique esse objeto item e retorne imediatamente. json.NewEncoder(w).Encode(value) escreve o valor Go escolhido como JSON; um slice vira um array e uma estrutura vira um objeto. O laço deve examinar todos os registros, não apenas items[0].

Depois disso, se nenhum ID corresponder ou o caminho estiver fora dessas rotas, defina 404 antes de escrever um erro JSON. w.WriteHeader(http.StatusNotFound) define o status. Um pequeno valor de erro compatível com JSON é map[string]string{"error": "item not found"}; a string error deve ser não vazia. Codifique esse map. Não escreva outra lista após um erro.

A primeira escrita de uma resposta normalmente confirma 200, então defina um status diferente antes de codificar. Encode retorna um error: nil significa ausência de falha. Neste exemplo, todos os valores são strings compatíveis com JSON. Um cliente desconectado ainda pode fazer a escrita falhar; enviar uma segunda resposta depois que o corpo começou não repara a conexão. Retornos antecipados também impedem que duas respostas sejam concatenadas.

Salve com Ctrl+O, confirme o nome existente handler.go com Enter e saia com Ctrl+X. Examine o arquivo salvo:

cat handler.go

Se abriu o arquivo errado, saia sem salvar: Ctrl+X e depois N se for perguntado se deseja salvar. Execute pwd para examinar sua localização, restaure o primeiro cd e reabra o nome correto. Se a confirmação de salvamento mostrar um nome não desejado, Ctrl+C a cancela. Não sobrescreva os dados de origem para compensar um erro de edição.

gofmt -w formata o arquivo Go indicado diretamente. O serve.py preparado compila seu código antes de substituir apenas o servidor registrado deste ambiente de trabalho. Ele não fornece implementação do handler. Um erro de compilação mantém o processo funcional anterior; uma resposta antiga do navegador não prova que o código novo compilou.

gofmt -w handler.go
python3 serve.py

Espere Lost and Found ready on port 8080. Se a formatação ou compilação falhar, leia o nome do arquivo e o número da linha. Reabra nano handler.go, encontre a expressão próxima com Ctrl+W, repare apenas essa região, salve, saia e repita ambos os comandos. Preserve a lógica já funcional; não restaure o backup inicial sobre o progresso posterior. Se aparecer No such file, restaure o primeiro cd. Os diagnósticos do serviço estão em .work/server.log, que pode ser lido com cat .work/server.log; um erro de digitação não é motivo para reiniciar a VM.

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Espere um objeto cujo id seja LF-103 e location seja Cafe counter, não um array nem o guarda-chuva da biblioteca. Verifique se a lista normal ainda retorna seu array de amostra:

curl -i 'http://127.0.0.1:8080/api/items'

Você acrescentou a seleção preservando o formato de coleção da página funcional. A próxima etapa amplia deliberadamente essa lista para todos os registros.

Retorne a coleção completa

Nesta etapa, transforme a lista de um registro em um catálogo completo somente de leitura sem perder os detalhes que escreveu. Comece em Workspace, em /home/labex/project/lost-found.

Antes de editar, leia a expressão de slice no ramo da coleção. items[:1] seleciona um prefixo que termina antes do índice um, ocultando os outros cinco registros. Preveja a resposta se o codificador receber o próprio items. Ao contrário de um array fixo de seis itens, esse argumento também funciona para outro slice fornecido.

LC_ALL=C.UTF-8 nano handler.go

Busque items[:1] com Ctrl+W e Enter. Altere apenas o valor codificado da coleção para o slice completo de entrada. Preserve o cabeçalho JSON, o laço de detalhes, o erro de ID ausente e os retornos antecipados. Salve no mesmo nome com Ctrl+O e Enter, depois Ctrl+X.

Uma coleção vazia deve ser codificada como [], nunca null. Um slice vazio diferente de nil é codificado como []; um slice nil seria codificado como null. A entrada vazia independente desta unidade é []Item{}. Não substitua um resultado vazio por erro de item inexistente: lista e detalhe têm significados diferentes. A ordem do array e das chaves dos objetos é flexível, mas nome, categoria e local pertencentes a cada ID devem permanecer juntos.

gofmt -w handler.go
python3 serve.py

Solicite a coleção e depois os dois guarda-chuvas pelos seus IDs separados:

curl -i 'http://127.0.0.1:8080/api/items'
curl -i 'http://127.0.0.1:8080/api/items/LF-101'
curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Espere todos os seis registros completos no array. LF-101 ainda pertence a Library desk e LF-103 a Cafe counter, embora ambos os nomes sejam Blue umbrella. Se algum detalhe mudou de lugar, examine a comparação de ID e qual valor do laço você codifica.

A próxima requisição usa deliberadamente um ID inexistente. Espere HTTP 404 e um objeto JSON com uma string error não vazia, em vez de uma lista vazia. Normalmente, curl ainda termina com sucesso ao receber uma resposta de erro HTTP: receber uma resposta é diferente de não conseguir se conectar.

curl -i 'http://127.0.0.1:8080/api/items/no-such-id'

Este handler ainda não filtra nem impõe o método HTTP; o próximo laboratório guiado introduz esses comportamentos. Não considere uma página inicial normal ou um 200 qualquer como prova de um endpoint de detalhes funcional. Agora você preservou lista e detalhes juntos enquanto alterava o alcance da coleção.

Escreva um teste que diferencie identidades

Nesta etapa, projete seu próprio teste específico do handler sem executar um segundo servidor real e depois use a página real. Comece em Workspace, no mesmo diretório.

Primeiro decida o que um teste deve detectar: um handler pode retornar o primeiro nome correspondente em vez do ID solicitado. Escolha dois pequenos registros próprios com o mesmo nome, mas IDs e locais diferentes, fora da coleção da página. Solicite o segundo ID. Anote o status, o ID e o local que devem ser retornados. Essa fixture independente faz uma resposta fixa do ambiente falhar e torna o teste útil além dos seis registros preparados.

Go descobre testes em arquivos que terminam em _test.go. Uma função de teste começa com Test e recebe t *testing.T. Crie seu arquivo de teste com nano:

LC_ALL=C.UTF-8 nano handler_test.go

Comece com package main. Importe encoding/json, net/http/httptest e testing. Acrescente uma função como func TestSecondIdentity(t *testing.T) { ... }, substituindo os pontos pelas instruções do teste. O corpo tem quatro partes:

Primeiro, construa sua fixture []Item de dois registros usando os nomes dos campos exportados de main.go. Passe-a para catalogHandler, em vez de carregar ou alterar items.json.

Depois, crie uma requisição para o detalhe do segundo registro. httptest.NewRequest("GET", path, nil) cria uma requisição; o último argumento significa ausência de corpo. httptest.NewRecorder() retorna um recorder de resposta. Chamar ServeHTTP(recorder, request) no handler executa o handler real e captura seus cabeçalhos, status e corpo.

Em seguida, declare var got Item. Decodifique com json.Unmarshal(recorder.Body.Bytes(), &got). &got é o endereço onde os campos serão armazenados. Verifique primeiro o error retornado: JSON inválido não deve ser confundido com uma estrutura vazia válida. Por exemplo, if err != nil { t.Fatal(err) } interrompe o teste com uma falha legível.

Depois disso, verifique o status e a identidade esperados. recorder.Code contém o status, e got.ID e got.Location contêm os campos decodificados. Compare-os ao segundo registro da fixture. Use if e t.Fatalf para falhar com os valores reais. %d exibe um número e %+v mostra os campos da estrutura. Não verifique apenas que algum JSON existe.

Use os IDs e locais escolhidos de forma consistente na requisição e nas verificações. Mantenha dados da fixture, execução e verificações na mesma função de teste para facilitar o raciocínio. Salve com Ctrl+O e Enter, depois Ctrl+X. O ambiente não fornece um teste aprovado para você.

go test -v . executa os testes deste pacote: -v mostra os nomes dos testes, e . seleciona o pacote atual.

gofmt -w handler_test.go
go test -v .

Espere o nome do seu teste seguido de PASS. Uma falha deve imprimir o status ou item real. Se falhar, decida se a relação esperada na fixture ou o handler está errado; corrija apenas essa parte com nano e execute o teste novamente. Um erro de compilação identifica arquivo e linha para examinar. Recompile com python3 serve.py após qualquer correção no handler para que o navegador também use o código atual. Mantenha pelo menos um teste de handler Go aprovado neste ambiente de trabalho; a verificação final executa seus testes isoladamente e verifica a API com outros registros de entrada fornecidos.

Mude para Lost and Found e recarregue. Espere 6 items found. Abra View item em LF-103 e leia sua referência e local de retirada; depois escolha Back to collection. Os dois guarda-chuvas devem continuar distinguíveis. Esta unidade oferece navegação pela coleção e pelos detalhes; o próximo laboratório guiado acrescenta controles de busca junto com o comportamento de filtragem da API.

A página real do catálogo com todos os registros de origem

Observe os seis cartões e suas referências individuais. A página consome sua API em execução e não contém uma coleção de reserva. Volte para Workspace se precisar corrigir algo. Agora você tem um catálogo utilizável e um teste cuja fixture e verificações você escolheu para expor um erro real de identidade.

Resumo

Você codificou registros Go como JSON, selecionou respostas de lista e detalhes pelo caminho, manteve nomes repetidos ligados aos seus próprios IDs e retornou um status real para um item inexistente. Também escreveu um teste isolado com httptest e usou a página em execução. A busca e o tratamento mais rigoroso de erros são a tarefa independente do próximo laboratório guiado.