From fd10dbbd13fbc34ccadea4a93495674fb78a225e Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Mon, 15 Apr 2024 13:56:10 +0200 Subject: [PATCH] add protobuf contract back to front; make pokemon structs internal --- .gitignore | 1 + Makefile | 11 ++ README.md | 5 + docs/docs.go | 32 +++-- docs/swagger.json | 32 +++-- docs/swagger.yaml | 26 ++-- go.mod | 7 +- go.sum | 25 +++- pb/pokemon.pb.go | 306 ++++++++++++++++++++++++++++++++++++++++++ pokemon/handler.go | 14 +- pokemon/pokemon.go | 48 ++++++- pokemon/pokemon.proto | 20 +++ pokemon/repo.go | 16 +-- pokemon/seeder.go | 8 +- rest/restutils.go | 13 ++ 15 files changed, 493 insertions(+), 71 deletions(-) create mode 100644 Makefile create mode 100644 pb/pokemon.pb.go create mode 100644 pokemon/pokemon.proto diff --git a/.gitignore b/.gitignore index 4b6d04c..f866ada 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ pokedex.db +pokedex diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb2fd6f --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY : build +.DEFAULT_GOAL := build + +build: + rm -rf pokedex* + swag init + protoc -I=./ --go_out=../ ./pokemon/pokemon.proto + go build + + + diff --git a/README.md b/README.md index 717efa0..035769f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,12 @@ A simple Go-powered REST API kata. - KISS: Use built-in `http` package. https://gin-gonic.com/ looks cooler but also adds dozens of dependencies 😮 No need for a "fully-featured" web framework. +## Protobuf + +- JSON mapping: https://protobuf.dev/programming-guides/proto3/#json + ## Swagger - Exposed at `http://localhost:8080/docs` - Regenerate with `swag init`, see https://github.com/swaggo/http-swagger +- Annotation format: see https://github.com/swaggo/swag \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 0db6e4d..a7c8ee0 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -27,13 +27,13 @@ const docTemplate = `{ "produces": [ "application/json" ], - "summary": "get all Pokemon", + "summary": "get all pokemon", "operationId": "get-all-pokemon", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/pokemon.Pokemon" + "$ref": "#/definitions/pb.Pokemons" } } } @@ -44,13 +44,13 @@ const docTemplate = `{ "produces": [ "application/json" ], - "summary": "Find a specific Pokemon by name", + "summary": "Find a specific pokemon by name", "operationId": "get-specific-pokemon", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/pokemon.Pokemon" + "$ref": "#/definitions/pb.Pokemons" } }, "500": { @@ -64,34 +64,27 @@ const docTemplate = `{ } }, "definitions": { - "pokemon.Move": { + "pb.Move": { "type": "object", "properties": { "name": { "type": "string" }, - "pokemonID": { - "description": "implicit; see https://gorm.io/docs/has_many.html", - "type": "integer" - }, "url": { "type": "string" } } }, - "pokemon.Pokemon": { + "pb.Pokemon": { "type": "object", "properties": { "height": { "type": "integer" }, - "id": { - "type": "integer" - }, "moves": { "type": "array", "items": { - "$ref": "#/definitions/pokemon.Move" + "$ref": "#/definitions/pb.Move" } }, "name": { @@ -101,6 +94,17 @@ const docTemplate = `{ "type": "integer" } } + }, + "pb.Pokemons": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/pb.Pokemon" + } + } + } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 1d648c6..4e71160 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -21,13 +21,13 @@ "produces": [ "application/json" ], - "summary": "get all Pokemon", + "summary": "get all pokemon", "operationId": "get-all-pokemon", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/pokemon.Pokemon" + "$ref": "#/definitions/pb.Pokemons" } } } @@ -38,13 +38,13 @@ "produces": [ "application/json" ], - "summary": "Find a specific Pokemon by name", + "summary": "Find a specific pokemon by name", "operationId": "get-specific-pokemon", "responses": { "200": { "description": "OK", "schema": { - "$ref": "#/definitions/pokemon.Pokemon" + "$ref": "#/definitions/pb.Pokemons" } }, "500": { @@ -58,34 +58,27 @@ } }, "definitions": { - "pokemon.Move": { + "pb.Move": { "type": "object", "properties": { "name": { "type": "string" }, - "pokemonID": { - "description": "implicit; see https://gorm.io/docs/has_many.html", - "type": "integer" - }, "url": { "type": "string" } } }, - "pokemon.Pokemon": { + "pb.Pokemon": { "type": "object", "properties": { "height": { "type": "integer" }, - "id": { - "type": "integer" - }, "moves": { "type": "array", "items": { - "$ref": "#/definitions/pokemon.Move" + "$ref": "#/definitions/pb.Move" } }, "name": { @@ -95,6 +88,17 @@ "type": "integer" } } + }, + "pb.Pokemons": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/pb.Pokemon" + } + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9811a12..0ff0978 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,30 +1,32 @@ basePath: / definitions: - pokemon.Move: + pb.Move: properties: name: type: string - pokemonID: - description: implicit; see https://gorm.io/docs/has_many.html - type: integer url: type: string type: object - pokemon.Pokemon: + pb.Pokemon: properties: height: type: integer - id: - type: integer moves: items: - $ref: '#/definitions/pokemon.Move' + $ref: '#/definitions/pb.Move' type: array name: type: string weight: type: integer type: object + pb.Pokemons: + properties: + entries: + items: + $ref: '#/definitions/pb.Pokemon' + type: array + type: object host: localhost:8080 info: contact: @@ -46,8 +48,8 @@ paths: "200": description: OK schema: - $ref: '#/definitions/pokemon.Pokemon' - summary: get all Pokemon + $ref: '#/definitions/pb.Pokemons' + summary: get all pokemon /pokemon/{name}: get: operationId: get-specific-pokemon @@ -57,10 +59,10 @@ paths: "200": description: OK schema: - $ref: '#/definitions/pokemon.Pokemon' + $ref: '#/definitions/pb.Pokemons' "500": description: error schema: type: string - summary: Find a specific Pokemon by name + summary: Find a specific pokemon by name swagger: "2.0" diff --git a/go.mod b/go.mod index 05543d4..064a50d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module pokedex go 1.22 require ( + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.16.3 + google.golang.org/protobuf v1.33.0 gorm.io/driver/sqlite v1.5.5 gorm.io/gorm v1.25.9 ) @@ -19,11 +22,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/swaggo/files v1.0.1 // indirect - github.com/swaggo/http-swagger v1.3.4 // indirect - github.com/swaggo/swag v1.16.3 // indirect golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect golang.org/x/tools v0.20.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8f77df5..fa92f2f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= @@ -9,18 +10,28 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= @@ -31,6 +42,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -39,14 +52,14 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -60,9 +73,13 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= diff --git a/pb/pokemon.pb.go b/pb/pokemon.pb.go new file mode 100644 index 0000000..7a97764 --- /dev/null +++ b/pb/pokemon.pb.go @@ -0,0 +1,306 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.33.0 +// protoc v5.26.1 +// source: pokemon/pokemon.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Move struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *Move) Reset() { + *x = Move{} + if protoimpl.UnsafeEnabled { + mi := &file_pokemon_pokemon_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Move) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Move) ProtoMessage() {} + +func (x *Move) ProtoReflect() protoreflect.Message { + mi := &file_pokemon_pokemon_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Move.ProtoReflect.Descriptor instead. +func (*Move) Descriptor() ([]byte, []int) { + return file_pokemon_pokemon_proto_rawDescGZIP(), []int{0} +} + +func (x *Move) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Move) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type Pokemons struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Entries []*Pokemon `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (x *Pokemons) Reset() { + *x = Pokemons{} + if protoimpl.UnsafeEnabled { + mi := &file_pokemon_pokemon_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Pokemons) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Pokemons) ProtoMessage() {} + +func (x *Pokemons) ProtoReflect() protoreflect.Message { + mi := &file_pokemon_pokemon_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Pokemons.ProtoReflect.Descriptor instead. +func (*Pokemons) Descriptor() ([]byte, []int) { + return file_pokemon_pokemon_proto_rawDescGZIP(), []int{1} +} + +func (x *Pokemons) GetEntries() []*Pokemon { + if x != nil { + return x.Entries + } + return nil +} + +type Pokemon struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Height int32 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` + Weight int32 `protobuf:"varint,3,opt,name=weight,proto3" json:"weight,omitempty"` + Moves []*Move `protobuf:"bytes,4,rep,name=moves,proto3" json:"moves,omitempty"` +} + +func (x *Pokemon) Reset() { + *x = Pokemon{} + if protoimpl.UnsafeEnabled { + mi := &file_pokemon_pokemon_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Pokemon) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Pokemon) ProtoMessage() {} + +func (x *Pokemon) ProtoReflect() protoreflect.Message { + mi := &file_pokemon_pokemon_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Pokemon.ProtoReflect.Descriptor instead. +func (*Pokemon) Descriptor() ([]byte, []int) { + return file_pokemon_pokemon_proto_rawDescGZIP(), []int{2} +} + +func (x *Pokemon) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Pokemon) GetHeight() int32 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *Pokemon) GetWeight() int32 { + if x != nil { + return x.Weight + } + return 0 +} + +func (x *Pokemon) GetMoves() []*Move { + if x != nil { + return x.Moves + } + return nil +} + +var File_pokemon_pokemon_proto protoreflect.FileDescriptor + +var file_pokemon_pokemon_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x70, 0x6f, 0x6b, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x6f, 0x6b, 0x65, 0x6d, 0x6f, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x04, 0x4d, 0x6f, 0x76, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2e, 0x0a, 0x08, 0x50, 0x6f, 0x6b, 0x65, 0x6d, 0x6f, 0x6e, + 0x73, 0x12, 0x22, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x50, 0x6f, 0x6b, 0x65, 0x6d, 0x6f, 0x6e, 0x52, 0x07, 0x65, 0x6e, + 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x6a, 0x0a, 0x07, 0x50, 0x6f, 0x6b, 0x65, 0x6d, 0x6f, 0x6e, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x77, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x4d, 0x6f, 0x76, 0x65, 0x52, 0x05, 0x6d, 0x6f, 0x76, 0x65, + 0x73, 0x42, 0x0c, 0x5a, 0x0a, 0x70, 0x6f, 0x6b, 0x65, 0x64, 0x65, 0x78, 0x2f, 0x70, 0x62, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pokemon_pokemon_proto_rawDescOnce sync.Once + file_pokemon_pokemon_proto_rawDescData = file_pokemon_pokemon_proto_rawDesc +) + +func file_pokemon_pokemon_proto_rawDescGZIP() []byte { + file_pokemon_pokemon_proto_rawDescOnce.Do(func() { + file_pokemon_pokemon_proto_rawDescData = protoimpl.X.CompressGZIP(file_pokemon_pokemon_proto_rawDescData) + }) + return file_pokemon_pokemon_proto_rawDescData +} + +var file_pokemon_pokemon_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_pokemon_pokemon_proto_goTypes = []interface{}{ + (*Move)(nil), // 0: Move + (*Pokemons)(nil), // 1: Pokemons + (*Pokemon)(nil), // 2: Pokemon +} +var file_pokemon_pokemon_proto_depIdxs = []int32{ + 2, // 0: Pokemons.entries:type_name -> Pokemon + 0, // 1: Pokemon.moves:type_name -> Move + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_pokemon_pokemon_proto_init() } +func file_pokemon_pokemon_proto_init() { + if File_pokemon_pokemon_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pokemon_pokemon_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Move); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pokemon_pokemon_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Pokemons); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pokemon_pokemon_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Pokemon); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pokemon_pokemon_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pokemon_pokemon_proto_goTypes, + DependencyIndexes: file_pokemon_pokemon_proto_depIdxs, + MessageInfos: file_pokemon_pokemon_proto_msgTypes, + }.Build() + File_pokemon_pokemon_proto = out.File + file_pokemon_pokemon_proto_rawDesc = nil + file_pokemon_pokemon_proto_goTypes = nil + file_pokemon_pokemon_proto_depIdxs = nil +} diff --git a/pokemon/handler.go b/pokemon/handler.go index dfbf2c0..530917f 100644 --- a/pokemon/handler.go +++ b/pokemon/handler.go @@ -7,10 +7,10 @@ import ( "pokedex/rest" ) -// @Summary Find a specific Pokemon by name +// @Summary Find a specific pokemon by name // @ID get-specific-pokemon // @Produce json -// @Success 200 {object} Pokemon +// @Success 200 {object} pb.Pokemons // @failure 500 {string} string "error" // @Router /pokemon/{name} [get] func HandleFindSingle(db *gorm.DB, rw http.ResponseWriter, req *http.Request) { @@ -24,15 +24,13 @@ func HandleFindSingle(db *gorm.DB, rw http.ResponseWriter, req *http.Request) { return } - rest.Json(rw, []Pokemon{ - pk, - }) + rest.ProtoJson(rw, pk.ToPbWrapped()) } -// @Summary get all Pokemon +// @Summary get all pokemon // @ID get-all-pokemon // @Produce json -// @Success 200 {object} Pokemon +// @Success 200 {object} pb.Pokemons // @Router /pokemon [get] func HandleFindAll(db *gorm.DB, rw http.ResponseWriter, req *http.Request) { repo := NewRepo(db) @@ -42,5 +40,5 @@ func HandleFindAll(db *gorm.DB, rw http.ResponseWriter, req *http.Request) { return } - rest.Json(rw, pokemons) + rest.ProtoJson(rw, ToPb(pokemons)) } diff --git a/pokemon/pokemon.go b/pokemon/pokemon.go index 5225360..a97044a 100644 --- a/pokemon/pokemon.go +++ b/pokemon/pokemon.go @@ -1,15 +1,57 @@ package pokemon -type Move struct { +import "pokedex/pb" + +type move struct { Name string Url string PokemonID uint // implicit; see https://gorm.io/docs/has_many.html } -type Pokemon struct { +func (m move) ToPb() *pb.Move { + return &pb.Move{ + Name: m.Name, + Url: m.Url, + } +} + +type pokemon struct { Id int Name string Height int Weight int - Moves []Move + Moves []move +} + +func ToPb(p []pokemon) *pb.Pokemons { + pbs := make([]*pb.Pokemon, len(p)) + for i, m := range p { + pbs[i] = m.ToPb() + } + + return &pb.Pokemons{ + Entries: pbs, + } +} + +func (p pokemon) ToPbWrapped() *pb.Pokemons { + pbs := make([]*pb.Pokemon, 1) + pbs = append(pbs, p.ToPb()) + return &pb.Pokemons{ + Entries: pbs, + } +} + +func (p pokemon) ToPb() *pb.Pokemon { + moves := make([]*pb.Move, len(p.Moves)) + for i, m := range p.Moves { + moves[i] = m.ToPb() + } + + return &pb.Pokemon{ + Name: p.Name, + Height: int32(p.Height), + Weight: int32(p.Weight), + Moves: moves, + } } diff --git a/pokemon/pokemon.proto b/pokemon/pokemon.proto new file mode 100644 index 0000000..96ebecb --- /dev/null +++ b/pokemon/pokemon.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +//package pb; -- https://protobuf.dev/programming-guides/proto3/#packages: ignored in Go + +option go_package = "pokedex/pb"; + +message Move { + string name = 1; + string url = 2; +} + +message Pokemons { + repeated Pokemon entries = 1; +} + +message Pokemon { + string name = 1; + int32 height = 2; + int32 weight = 3; + repeated Move moves = 4; +} \ No newline at end of file diff --git a/pokemon/repo.go b/pokemon/repo.go index c72d266..f9817b0 100644 --- a/pokemon/repo.go +++ b/pokemon/repo.go @@ -15,19 +15,19 @@ func NewRepo(db *gorm.DB) Repo { } } -func (r Repo) Find(name string) (Pokemon, error) { - var pokemon Pokemon - result := r.db.Find(&pokemon, "name = ?", name) // .First() generates an error if none found +func (r Repo) Find(name string) (pokemon, error) { + var poke pokemon + result := r.db.Find(&poke, "name = ?", name) // .First() generates an error if none found if result.Error != nil { - return Pokemon{}, fmt.Errorf("Unable to retrive pokemon named %s: %w", name, result.Error) + return pokemon{}, fmt.Errorf("Unable to retrive pokemon named %s: %w", name, result.Error) } - return pokemon, nil + return poke, nil } -func (r Repo) FindAll() ([]Pokemon, error) { - var pokemons []Pokemon - result := r.db.Model(&Pokemon{}).Preload("Moves").Find(&pokemons) +func (r Repo) FindAll() ([]pokemon, error) { + var pokemons []pokemon + result := r.db.Model(&pokemon{}).Preload("Moves").Find(&pokemons) if result.Error != nil { return nil, fmt.Errorf("Unable to retrive pokemons: %w", result.Error) } diff --git a/pokemon/seeder.go b/pokemon/seeder.go index c3b5729..b055cd6 100644 --- a/pokemon/seeder.go +++ b/pokemon/seeder.go @@ -3,13 +3,13 @@ package pokemon import "gorm.io/gorm" func Seed(db *gorm.DB) { - pokemons := []Pokemon{ + pokemons := []pokemon{ { Id: 0, Name: "Jaak", Height: 0, Weight: 0, - Moves: []Move{ + Moves: []move{ { Name: "Boojakasja", Url: "http://sjakka.be", @@ -22,8 +22,8 @@ func Seed(db *gorm.DB) { }, } - db.AutoMigrate(&Pokemon{}) - db.AutoMigrate(&Move{}) + db.AutoMigrate(&pokemon{}) + db.AutoMigrate(&move{}) for _, pokemon := range pokemons { res := db.Create(&pokemon) diff --git a/rest/restutils.go b/rest/restutils.go index 075fd62..8b7f700 100644 --- a/rest/restutils.go +++ b/rest/restutils.go @@ -2,6 +2,8 @@ package rest import ( "encoding/json" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "net/http" ) @@ -16,6 +18,17 @@ func Json(w http.ResponseWriter, data any) { w.Write(bytes) } +func ProtoJson(w http.ResponseWriter, msg proto.Message) { + bytes, err := protojson.Marshal(msg) + if err != nil { + http.Error(w, "Oops, something went wrong", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(bytes) +} + func BadRequest(w http.ResponseWriter) { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) }