├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── go.mod ├── go.sum ├── server.go └── server_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | src 26 | goCep 27 | vendor/ 28 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - "1.13.x" 4 | script: env GO111MODULE=on go test -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) [year], [fullname] 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goCep 2 | 3 | Projeto em Golang para buscar dados do CEP e armazenar em cache 4 | 5 | 6 | # Compilar 7 | 8 | Além de ter o Go instalado no sistema operacional é necessário executar: 9 | 10 | go build 11 | 12 | # Executar 13 | 14 | O binário chamado goCep será criado. Basta executá-lo e ele ficará ouvindo na porta 3000 por novas requisições 15 | 16 | 17 | # Testando 18 | Para rodar os tests, você pode executar: 19 | 20 | go test 21 | 22 | # Uso 23 | 24 | Basta acessar a URL como no exemplo abaixo 25 | 26 | http://localhost:3000/cep/89201405 27 | 28 | O retorno será um JSON com o conteúdo 29 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eminetto/goCep 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f 7 | github.com/gorilla/mux v1.4.0 8 | github.com/ryanuber/go-filecache v0.0.0-20140809201847-52ce07fafe23 9 | github.com/stretchr/testify v1.4.0 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andelf/go-curl v0.0.0-20170111140632-f8b334df3789 h1:VAvQSXDxm3HBfghs3MBPUfJLLKDCzu/bGSe6gU+1T6c= 2 | github.com/andelf/go-curl v0.0.0-20170111140632-f8b334df3789/go.mod h1:WO1d2m1QDzkoPcgn9lgHVMi7qQR5j3jxYjIIvMTHpC0= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkHBdPZU4jo9bSmrLpII768arSyMFgk= 6 | github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 7 | github.com/gorilla/mux v1.4.0 h1:N6R8isjoRv7IcVVlf0cTBbo0UDc9V6ZXWEm0HQoQmLo= 8 | github.com/gorilla/mux v1.4.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/ryanuber/go-filecache v0.0.0-20140809201847-52ce07fafe23 h1:/HZBU36SDxu8fMJsWDlWAFz5Z7Ejrt8vwJqiIQqGQoI= 12 | github.com/ryanuber/go-filecache v0.0.0-20140809201847-52ce07fafe23/go.mod h1:Gx7ypi/UAGQpEpxF161RzcRnnQzgVLp7ktM/Oi2wcrU= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 15 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 18 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 19 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/gorilla/context" 8 | "github.com/gorilla/mux" 9 | "github.com/ryanuber/go-filecache" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "os" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | const serverPort = "3000"; 20 | const cacheTime = 500; 21 | 22 | type Cep struct { 23 | Cep string `json:"cep"` 24 | Logradouro string `json:"logradouro"` 25 | Complemento string `json:"complemento"` 26 | Bairro string `json:"bairro"` 27 | Localidade string `json:"localidade"` 28 | Uf string `json:"uf"` 29 | Unidade string `json:"unidade"` 30 | Ibge string `json:"ibge"` 31 | Gia string `json:"gia"` 32 | } 33 | 34 | func main() { 35 | errorMessage := "Erro lendo CEP" 36 | router := mux.NewRouter() 37 | 38 | router.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { 39 | _, err := rw.Write([]byte([]byte("ping"))) 40 | if err != nil { 41 | respondWithError(rw, http.StatusUnauthorized, err.Error(), errorMessage) 42 | return 43 | } 44 | }) 45 | router.HandleFunc("/cep/{id}", func(rw http.ResponseWriter, req *http.Request) { 46 | vars := mux.Vars(req) 47 | rw.Header().Set("Content-Type", "application/json") 48 | cep, err := getCep(vars["id"]) 49 | if err != nil { 50 | respondWithError(rw, http.StatusUnauthorized, err.Error(), errorMessage) 51 | return 52 | } 53 | _, err = rw.Write([]byte(cep)) 54 | if err != nil { 55 | respondWithError(rw, http.StatusUnauthorized, err.Error(), errorMessage) 56 | return 57 | } 58 | }) 59 | http.Handle("/", router) 60 | logger := log.New(os.Stderr, "logger: ", log.Lshortfile) 61 | srv := &http.Server{ 62 | ReadTimeout: 30 * time.Second, 63 | WriteTimeout: 30 * time.Second, 64 | Addr: ":"+serverPort, 65 | Handler: context.ClearHandler(http.DefaultServeMux), 66 | ErrorLog: logger, 67 | } 68 | err := srv.ListenAndServe() 69 | if err != nil { 70 | panic(err) 71 | } 72 | } 73 | 74 | func getCep(id string) (string, error) { 75 | cached := getFromCache(id) 76 | if cached != "" { 77 | return cached, nil 78 | } 79 | req, err := http.Get(fmt.Sprintf("http://viacep.com.br/ws/%s/json/", id)) 80 | if err != nil { 81 | return "", err 82 | } 83 | 84 | var c Cep 85 | err = json.NewDecoder(req.Body).Decode(&c) 86 | if err != nil { 87 | return "", err 88 | } 89 | res, err := json.Marshal(c) 90 | if err != nil { 91 | return "", err 92 | } 93 | 94 | return saveOnCache(id, string(res)), nil 95 | } 96 | 97 | func getFromCache(id string) string { 98 | updater := func(path string) error { 99 | return errors.New("expired") 100 | } 101 | 102 | fc := filecache.New(getCacheFilename(id), cacheTime*time.Second, updater) 103 | 104 | fh, err := fc.Get() 105 | if err != nil { 106 | return "" 107 | } 108 | 109 | content, err := ioutil.ReadAll(fh) 110 | if err != nil { 111 | return "" 112 | } 113 | 114 | return string(content) 115 | } 116 | 117 | func saveOnCache(id string, content string) string { 118 | updater := func(path string) error { 119 | f, err := os.Create(path) 120 | if err != nil { 121 | return err 122 | } 123 | defer f.Close() 124 | _, err = f.Write([]byte(content)) 125 | return err 126 | } 127 | 128 | fc := filecache.New(getCacheFilename(id), cacheTime*time.Second, updater) 129 | 130 | _, err := fc.Get() 131 | if err != nil { 132 | return "" 133 | } 134 | 135 | return content 136 | } 137 | 138 | func getCacheFilename(id string) string { 139 | return os.TempDir()+"/cep"+strings.Replace(id, "-", "", -1) 140 | } 141 | 142 | //RespondWithError return a http error 143 | func respondWithError(w http.ResponseWriter, code int, e string, message string) { 144 | respondWithJSON(w, code, map[string]string{"code": strconv.Itoa(code), "error": e, "message": message}) 145 | } 146 | 147 | func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { 148 | response, _ := json.Marshal(payload) 149 | 150 | w.Header().Set("Content-Type", "application/json") 151 | w.WriteHeader(code) 152 | w.Write(response) 153 | } 154 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/stretchr/testify/assert" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | 11 | 12 | func removeCacheFile(t *testing.T, id string) { 13 | assert.Nil(t, os.Remove(getCacheFilename(id))) 14 | } 15 | 16 | func Test_getCacheFilename(t *testing.T) { 17 | id := "89201405" 18 | idWithDash := "89201-405" 19 | 20 | assert.Equal(t, getCacheFilename(id), os.TempDir()+"/cep"+id) 21 | assert.Equal(t, getCacheFilename(idWithDash), os.TempDir()+"/cep"+id) 22 | } 23 | 24 | func Test_getCep(t *testing.T) { 25 | t.Run("invalid cep", func(t *testing.T) { 26 | id := "0000000" 27 | wrongCep, err := getCep(id) 28 | assert.Equal(t, "", wrongCep) 29 | assert.Error(t, err) 30 | }) 31 | t.Run("valid cep", func(t *testing.T) { 32 | 33 | id := "60170150" 34 | cepJson, err := getCep(id) 35 | assert.Nil(t, err) 36 | res := Cep{} 37 | assert.Nil(t, json.Unmarshal([]byte(cepJson), &res)) 38 | 39 | assert.Equal(t, "60170-150", res.Cep) 40 | assert.Equal(t, "Rua Vicente Leite", res.Logradouro) 41 | assert.Equal(t, "até 879/880", res.Complemento) 42 | assert.Equal(t, "Meireles", res.Bairro) 43 | assert.Equal(t, "Fortaleza", res.Localidade) 44 | assert.Equal(t, "CE", res.Uf) 45 | assert.Equal(t, "", res.Unidade) 46 | assert.Equal(t, "2304400", res.Ibge) 47 | assert.Equal(t, "", res.Gia) 48 | 49 | removeCacheFile(t, id) 50 | }) 51 | } 52 | 53 | func Test_Cache(t *testing.T) { 54 | id := "89201405" 55 | _, err := getCep(id) // Add to temporary_directory_path/cep89201405 56 | assert.Nil(t, err) 57 | _, err = os.Stat(getCacheFilename(id)) 58 | assert.Nil(t, err) 59 | removeCacheFile(t,id) 60 | } 61 | --------------------------------------------------------------------------------