├── .github └── workflows │ └── pull-request.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── example ├── helpers │ └── helpers.go ├── message.pb.go ├── message.proto ├── model │ └── model.go ├── nulls │ └── nulls.go └── transform │ ├── custom_transformer.go │ ├── message_transformer.go │ └── options.go ├── generator ├── doc.go ├── error.go ├── field.go ├── field_test.go ├── file.go ├── file_test.go ├── generator_suite_test.go ├── message.go ├── message_options.go ├── message_test.go ├── oneof.go ├── oneof_test.go ├── option_extractor.go ├── print.go ├── request.go ├── request_test.go ├── template.go ├── template_test.go ├── testdata │ ├── model.go │ └── processfile.go.golden └── types.go ├── go.mod ├── go.sum ├── main.go ├── options ├── annotations.pb.go ├── annotations.proto └── doc.go └── source ├── doc.go ├── field.go ├── parser.go ├── parser_test.go └── source_suite_test.go /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - "master" 8 | 9 | jobs: 10 | go-tests: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - uses: actions/setup-go@v4 18 | with: 19 | go-version-file: 'go.mod' 20 | 21 | - name: Install ginkgo 22 | run: go install github.com/onsi/ginkgo/v2/ginkgo@latest 23 | 24 | - name: Run tests 25 | run: ginkgo -r 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.coverprofile 2 | coverage.* 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to proto-gen-struct-transformer 2 | 3 | Thank you for your contribution to this project. Next several steps describe 4 | process of contribution: 5 | 6 | - Please, open an issue first and describe what problem you are trying to solve. 7 | - Make changes. 8 | - Add test(s) for new code. 9 | - If your changes modify plugin's output, please, add an appropriate example to `example` directory and re-generate it with `make generate`. 10 | - Run `ginkgo -r -cover` on your feature branch and master branch. New feature should not decrease test coverage. 11 | - Open PR on GitHub. 12 | 13 | # Developers tools 14 | - [Protocol buffers compiler (protoc)](https://github.com/protocolbuffers/protobuf) - Google's data interchange format. 15 | - [protoc-gen-gogofaster](https://github.com/gogo/protobuf/tree/master/protoc-gen-gogofaster) - protoc plugin implements Go bindings for protocol buffers. 16 | - [goimports](https://golang.org/x/tools/cmd/goimports) - Command goimports updates your Go import lines, adding missing ones and removing unreferenced ones. 17 | - [Ginkgo](https://github.com/onsi/ginkgo#set-me-up) - BDD Testing Framework for Go. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Bold Commerce. 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 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. 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 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION=1.0.7-dev 2 | BUILDTIME=$(shell date +"%Y-%m-%dT%T%z") 3 | LDFLAGS= -ldflags '-X github.com/bold-commerce/protoc-gen-struct-transformer/generator.version=$(VERSION) -X github.com/bold-commerce/protoc-gen-struct-transformer/generator.buildTime=$(BUILDTIME)' 4 | 5 | .PHONY: re-generate-example generate install build version setup 6 | 7 | re-generate-example: 8 | protoc \ 9 | --proto_path=$(GOPATH)/pkg/mod/github.com/gogo:. \ 10 | --struct-transformer_out=package=transform,debug=false,helper-package=helpers,goimports=true:. \ 11 | --gogofaster_out=Moptions/annotations.proto=github.com/bold-commerce/protoc-gen-struct-transformer/options:. \ 12 | ./example/message.proto 13 | 14 | generate: version re-generate-example 15 | 16 | re-generate-example-debug: 17 | protoc \ 18 | --proto_path=$(GOPATH)/pkg/mod/github.com/gogo:. \ 19 | --struct-transformer_out=package=transform,debug=true,helper-package=helpers,goimports=true:. \ 20 | --gogofaster_out=Moptions/annotations.proto=github.com/bold-commerce/protoc-gen-struct-transformer/options:. \ 21 | ./example/message.proto 22 | 23 | generate-debug: version re-generate-example-debug 24 | 25 | generate-annotations: 26 | protoc \ 27 | --proto_path=$(GOPATH)/pkg/mod/github.com/gogo:. \ 28 | --gogofaster_out=Moptions/annotations.proto=github.com/bold-commerce/protoc-gen-struct-transformer/options:. \ 29 | ./options/annotations.proto 30 | 31 | install: setup 32 | go install $(LDFLAGS) 33 | 34 | build: OUTPUT=. 35 | build: setup 36 | go build $(LDFLAGS) -o $(OUTPUT) 37 | 38 | version: 39 | protoc-gen-struct-transformer --version 40 | 41 | setup: 42 | go mod download 43 | go mod verify -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transformation function generator for gRPC. 2 | 3 | [![GoDoc](https://godoc.org/github.com/bold-commerce/protoc-gen-struct-transformer?status.svg)](https://godoc.org/github.com/bold-commerce/protoc-gen-struct-transformer) 4 | [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/bold-commerce/protoc-gen-struct-transformer?sort=semver)](https://github.com/bold-commerce/protoc-gen-struct-transformer/releases) 5 | [![BSD-3-Clause](https://img.shields.io/github/license/bold-commerce/protoc-gen-struct-transformer)](./LICENSE) 6 | 7 | 8 | 9 | * [Quick presentation](#quick-presentation) 10 | * [Overview](#overview) 11 | * [How to use](#how-to-use) 12 | * [Installation](#installation) 13 | * [Homebrew](#homebrew) 14 | * [go get](#go-get) 15 | * [Add options to *.proto file](#add-options-to-proto-file) 16 | * [Run protoc](#run-protoc) 17 | * [Use generated functions in your gRPC server implementation.](#use-generated-functions-in-your-grpc-server-implementation) 18 | * [CLI parameters](#cli-parameters) 19 | * [Troubleshooting](#troubleshooting) 20 | * [make generate returns an error](#make-generate-returns-an-error) 21 | * ["protobuf@v1.3.1/gogoproto/gogo.proto" was not found or had errors.](#protobufv131gogoprotogogoproto-was-not-found-or-had-errors) 22 | 23 | 24 | 25 | ## Quick presentation 26 | [Speakerdeck](https://speakerdeck.com/ekhabarov/protoc-gen-struct-transformer) 27 | 28 | ## Overview 29 | [Protocol buffers complier](https://github.com/protocolbuffers/protobuf) `protoc` generated structures based on message 30 | definition in `*.proto` file. It's possible to use these generated structures 31 | directly, but it's better to have clear separation between transport level 32 | (gRPC) and business logic with its own structures. In this case you have to 33 | convert protobuf structures into business logic structures and vice versa. 34 | 35 | `protoc-gen-struct-transformer` is a plugin for `protoc` which generates functions 36 | for structure transformation. 37 | 38 | Let's look at simple example. 39 | 40 | Source proto file: 41 | ```proto 42 | // message.proto 43 | syntax = "proto3"; 44 | package messages; 45 | 46 | message Product { 47 | int32 id = 1; 48 | string name = 2; 49 | } 50 | ``` 51 | 52 | Command `protoc --gogofaster_out=. message.proto` will generate `message.pb.go` with 53 | following structure: 54 | ```go 55 | type Product struct { 56 | Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 57 | Name string `protobuf:"bytes,2,opt,name=name,json=name,proto3" json:"name,omitempty"` 58 | } 59 | ``` 60 | and let's suppose you service has as `repo` package with `ProductModel` struct inside: 61 | 62 | ```go 63 | type ProductModel struct { 64 | ID int `db:"id" json:"id"` 65 | Name string `db:"name" json:"name"` 66 | } 67 | ``` 68 | In order to publish data from the repo to API you have to convert `ProductModel` 69 | to `Product`, for saving data which hit API you have to make back transformation. 70 | 71 | ```go 72 | func ProductModelToProduct(m repo.ProductModel) proto.Product { 73 | return proto.Product { 74 | Id: m.ID, 75 | Name: m.Name, 76 | } 77 | } 78 | 79 | func ProductToProductModel(p proto.Product) repo.ProductModel { 80 | return repo.ProductModel { 81 | ID: p.Id, 82 | Name: p.Name, 83 | } 84 | } 85 | ``` 86 | List of function type should be generated: 87 | 88 | Source | Destination | Suffix name 89 | ---------|-------------|--- 90 | *proto | *model | `Ptr` 91 | []*proto | []*model | `PtrList` 92 | *proto | model | `PtrVal` 93 | []*proto | []model | `PtrValList` 94 | proto | model | 95 | proto | \*model | `ValPtr` 96 | []proto | []model | `ValList` 97 | *model | *proto | `Ptr` 98 | []*model | []*proto | `PtrList` 99 | *model | proto | `PtrVal` 100 | []*model | []proto | `PtrValList` 101 | model | proto | 102 | model | \*proto | `ValPtr` 103 | []model | []proto | `ValList` 104 | 105 | function name has a format `To`. 106 | 107 | For instance, function which converts list of pointers to Product into list of 108 | Product values will be named `PbToProductPtrValList`, 109 | where 110 | * `Pb` is a replacement for proto message 111 | * `Products` is a name of model structure 112 | * `PtrValList` is a suffix pointed that convertion is made from slice of pointer to slice of values. 113 | 114 | Full set of function for Product message will be as: 115 | ```go 116 | func PbToProductPtr(src *example.Product, opts ...TransformParam) *model.Product 117 | func PbToProductPtrList(src []*example.Product, opts ...TransformParam) []*model.Product 118 | func PbToProductPtrVal(src *example.Product, opts ...TransformParam) model.Product 119 | func PbToProductPtrValList(src []*example.Product, opts ...TransformParam) []model.Product 120 | func PbToProductList(src []*example.Product, opts ...TransformParam) []model.Product 121 | func PbToProduct(src example.Product, opts ...TransformParam) model.Product 122 | func PbToProductValPtr(src example.Product, opts ...TransformParam) *model.Product 123 | func PbToProductValList(src []example.Product, opts ...TransformParam) []model.Product 124 | func ProductToPbPtr(src *model.Product, opts ...TransformParam) *example.Product 125 | func ProductToPbPtrList(src []*model.Product, opts ...TransformParam) []*example.Product 126 | func ProductToPbPtrVal(src *model.Product, opts ...TransformParam) example.Product 127 | func ProductToPbValPtrList(src []model.Product, opts ...TransformParam) []*example.Product 128 | func ProductToPbList(src []model.Product, opts ...TransformParam) []*example.Product 129 | func ProductToPb(src model.Product, opts ...TransformParam) example.Product 130 | func ProductToPbValPtr(src model.Product, opts ...TransformParam) *example.Product 131 | func ProductToPbValList(src []model.Product, opts ...TransformParam) []example.Product 132 | ``` 133 | 134 | where 135 | * `example` is a package generated by `protoc-gen-go` or `protoc-gen-gogo` plugin 136 | * `model` is a package which contains manually created models structures. 137 | 138 | Full example you can find in [example](./example) directory. 139 | 140 | ## How to use 141 | 142 | ### Installation 143 | I assume you already have `protoc` installed. 144 | 145 | First of all, it's necessary to install plugin itself, it's just a binary file, 146 | which should be placed into $PATH to be available for `protoc`. 147 | 148 | #### Homebrew 149 | 150 | ```shell 151 | % brew tap bold-commerce/tap 152 | % brew install protoc-gen-struct-transformer 153 | ``` 154 | 155 | #### go get 156 | If you're going to make changes to plugin, use `go get ...` or `git clone ...` 157 | 158 | ```shell 159 | % export GO111MODULE=on 160 | % go get -u -d github.com/bold-commerce/protoc-gen-struct-transformer 161 | % cd $GOPATH/src/github.com/bold-commerce/protoc-gen-struct-transformer 162 | // make changes 163 | % go install 164 | ``` 165 | 166 | Next, we need `protoc-gen-go` plugin (or `protoc-gen-gogofaster` if you use 167 | `gogo` specific options) which creates `*.pb.go` file. 168 | ```shell 169 | go get -u github.com/golang/protobuf/protoc-gen-go 170 | // or 171 | go get -u github.com/gogo/protobuf/protoc-gen-gogofaster 172 | ``` 173 | 174 | ### Add options to *.proto file 175 | To configure plugin you have to use **file level** options listed below. The 176 | plugin will not process file without these options. 177 | ```proto 178 | // This import allows to use this options. 179 | // Relatively to import_path: github.com/bold-commerce/protoc-gen-struct-transformer. 180 | // See Makefile for mapping details. 181 | import "options/annotations.proto"; 182 | 183 | // Go package name which contains business logic structures. 184 | option (transformer.go_repo_package) = "models"; 185 | // Go package name with protobuf generated srtuctures. Could be equal to 186 | // options go_package. 187 | option (transformer.go_protobuf_package) = "example"; 188 | // Path to source file with Go structures which will be used as destination. 189 | option (transformer.go_models_file_path) = "example/model/model.go"; 190 | ``` 191 | as well as **message level** option 192 | ```proto 193 | // Name of structure from business logic package. This option links business 194 | // logic and generated structure. 195 | message Product { 196 | option (transformer.go_struct) = "ProductModel"; 197 | // ... 198 | } 199 | ``` 200 | options above are minimal requirement for use this plugin. 201 | 202 | Also plugin has additional **field level** options: 203 | 204 | ```proto 205 | message Product { 206 | // SomeField will not be added to transformation function. 207 | string some_field = 4 [ (transformer.skip) = true ]; 208 | // "map_as" option is used in cases when protoc-gen-go* plugin creates 209 | // "unpredictable" field name, i.e. by default protoc-gen-go* converts 210 | // protobuf named writen in snake_case into CamelCase, but for fields like 211 | // map_field_1 this rule has aa exception, in pb.go file it will be 212 | // "MapField_1" instead of "MapField1". 213 | // "map_to" options is used when you need to map current message field to 214 | // field in model with arbitrary name. 215 | // Both options "map_as" and "map_to" can be used independently. 216 | string map_field_1 = 6 [ (transformer.map_as) = "MapField_1", (transformer.map_to) = "MapField1"]; 217 | // "custom" allows to use custom transformers for fields, which require extended transformation 218 | // The plugin won't generate methods for this field, 219 | // but rather expect it to be in the same package with the transformer file 220 | CustomType custom_field [(transformer.custom) = true] 221 | } 222 | ``` 223 | ### Run protoc 224 | ```shell 225 | protoc \ 226 | --proto_path=github.com/gogo:. \ 227 | --go_out=Moptions/annotations.proto=github.com/bold-commerce/protoc-gen-struct-transformer/options,plugins=grpc:. \ 228 | --struct-transformer_out=package=transform:. \ 229 | ./message.proto 230 | ``` 231 | this command generates two files: 232 | * `message.pb.go` contains auto-generated structures. 233 | * `transform/message_transformer.go` contains transformation functions. 234 | 235 | by default `message_transformer.go` does not contain imports. To add imports 236 | run `protoc` with: 237 | ```shell 238 | --struct-transformer_out=package=transform,goimports=true:. \ 239 | ``` 240 | 241 | ### Use generated functions in your gRPC server implementation. 242 | ```go 243 | func (s *server) CreateProduct(ctx context.Context, req *pb.Request) (*pb.Response, error) { 244 | p, err := s.svc.Create(ctx, transform.PbToProduct(req.Product)) 245 | if err != nil { 246 | return nil, err 247 | } 248 | 249 | return &pb.Response{ 250 | Product: transform.ProductToPb(p), 251 | }, nil 252 | } 253 | ``` 254 | 255 | ### CLI parameters 256 | ``` 257 | Usage of protoc-gen-struct-transformer: 258 | -debug 259 | Add debug information to generated file. 260 | -goimports 261 | Perform goimports on generated file. 262 | -helper-package string 263 | Package name for helper functions. 264 | -package string 265 | Package name for generated functions. (default "fallback") 266 | -use-package-in-path 267 | If true, package parameter will be used in path for output file. (default true) 268 | -version 269 | Print current version. 270 | ``` 271 | ## Troubleshooting 272 | 273 | ### make generate returns an error 274 | #### "protobuf@v1.3.1/gogoproto/gogo.proto" was not found or had errors. 275 | `gogo.proto` file which is used for gogo-specific options is imported from 276 | go modules cache. In order to fill out the cache run: 277 | 278 | ```shell 279 | % export GO111MODULE=on 280 | % go build 281 | ``` 282 | and run `make generate` again. 283 | -------------------------------------------------------------------------------- /example/helpers/helpers.go: -------------------------------------------------------------------------------- 1 | // Package helpers contains manually written functions for transforming custom 2 | // types. Such package should be written by end-user because plugin has no idea 3 | // which types could be used. It generates just function names for non-standard 4 | // types. 5 | package helpers 6 | 7 | import ( 8 | "strconv" 9 | "time" 10 | 11 | "github.com/bold-commerce/protoc-gen-struct-transformer/example/nulls" 12 | ) 13 | 14 | func TimeToNullsTime(t time.Time) nulls.Time { 15 | return nulls.Time{Time: t} 16 | } 17 | 18 | func NullsTimeToTime(nt nulls.Time) time.Time { 19 | return nt.Time 20 | } 21 | 22 | func TimePtrToNullsTimePtr(t *time.Time) *nulls.Time { 23 | return &nulls.Time{Time: *t} 24 | } 25 | 26 | func NullsTimePtrToTimePtr(nt *nulls.Time) *time.Time { 27 | return &nt.Time 28 | } 29 | 30 | func TimePtrToNullsTime(t *time.Time) nulls.Time { 31 | return nulls.Time{Time: *t} 32 | } 33 | 34 | func NullsTimeToTimePtr(nt nulls.Time) *time.Time { 35 | return &nt.Time 36 | } 37 | 38 | func Int32ToString(i int32) string { 39 | return strconv.FormatInt(int64(i), 10) 40 | } 41 | 42 | // StringToInt32 converts string to int32. It doesn't return an error for now, 43 | // and if string is not correct or value is aut of range of int32 it will return 44 | // default int32 value which is 0. 45 | func StringToInt32(s string) int32 { 46 | i, err := strconv.ParseInt(s, 10, 32) 47 | if err != nil { 48 | return 0 49 | } 50 | 51 | return int32(i) 52 | } 53 | 54 | func Int64ToString(i int64) string { 55 | return strconv.FormatInt(i, 10) 56 | } 57 | 58 | // StringToInt64 converts string to int63. For details see comments for 59 | // StringToInt32 function. 60 | func StringToInt64(s string) int64 { 61 | i, err := strconv.ParseInt(s, 10, 64) 62 | if err != nil { 63 | return 0 64 | } 65 | 66 | return i 67 | } 68 | -------------------------------------------------------------------------------- /example/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package svc.example; 3 | 4 | option (transformer.go_repo_package) = "model"; 5 | option (transformer.go_protobuf_package) = "example"; 6 | option (transformer.go_models_file_path) = "example/model/model.go"; 7 | option go_package = "example"; // Package name for pb.go 8 | 9 | import "options/annotations.proto"; 10 | import "protobuf@v1.3.1/gogoproto/gogo.proto"; // for gogoproto options 11 | import "google/protobuf/timestamp.proto"; 12 | 13 | message TheOne{ 14 | oneof the_decl { 15 | string string_value = 1; 16 | int64 int64_value = 2; 17 | } 18 | } 19 | 20 | message NotSupportedOneOf{ 21 | oneof the_decl { 22 | bool bool_value = 1; 23 | string string_value = 2; 24 | } 25 | } 26 | 27 | message CustomOneof{ 28 | oneof value { 29 | string string_value = 1; 30 | int64 int64_value = 2; 31 | } 32 | } 33 | 34 | 35 | message CustomType{ 36 | string value = 1; 37 | } 38 | 39 | message Product { 40 | option (transformer.go_struct) = "Product"; 41 | 42 | int32 id = 1; 43 | string name = 2; 44 | TheOne one = 3; 45 | TheOne second_id = 4; 46 | // Example of the custom transformer for the struct 47 | CustomType custom_field = 5 [(transformer.custom) = true]; 48 | // Example of the custom transformer for the struct with oneof type in it 49 | CustomOneof custom_oneof = 6 [(transformer.custom) = true]; 50 | // Currently the plugin does not support oneof types 51 | // rather than the specific example with `int64_value` and `string_value` 52 | // In current implementation it generates the PbToPtrVal and ToPbValPtr 53 | // TODO: change these method names to include either field name or field type to it 54 | // Changing method names will break backward compatibility with previous versions of the plugin 55 | NotSupportedOneOf notsupported_oneof = 7; 56 | } 57 | 58 | message Order { 59 | option (transformer.go_struct) = "Order"; 60 | 61 | int64 id = 1; 62 | TheOne first_id = 2; 63 | TheOne second_id = 3; 64 | TheOne third_url = 4; 65 | } 66 | 67 | message Address { 68 | option (transformer.go_struct) = "Address"; 69 | 70 | int64 id = 1; 71 | string type = 2; 72 | } 73 | 74 | message Customer { 75 | option (transformer.go_struct) = "Customer"; 76 | 77 | int64 id = 1; 78 | string name = 2; 79 | 80 | repeated Address addresses = 3; 81 | Address default_address = 4; 82 | Address billing_address = 5 [ (gogoproto.nullable) = false ]; 83 | 84 | string map_field_1 = 6 [ 85 | (transformer.map_as) = "MapField_1", 86 | (transformer.map_to) = "MapField1" 87 | ]; 88 | string map_field_to_without_digits = 7 [ (transformer.map_to) = "MapField2" ]; 89 | } 90 | 91 | 92 | // opposite message order, usage of LineItem is earlier than message is defined. 93 | message LineItemUsage { 94 | option (transformer.go_struct) = "MyLineItemUsage"; 95 | 96 | LineItem Item = 1; 97 | repeated LineItem List = 2; 98 | } 99 | 100 | message LineItem { 101 | option (transformer.go_struct) = "MyLineItem"; 102 | 103 | // Capitalized ID. It's not by protobuf style guide, but supported too. 104 | int64 ID = 1; // ID-> ID, iD -> ID, id -> Id 105 | string Type = 2; 106 | // SomeField will not be added to transformation function. 107 | string some_field = 3 [ (transformer.skip) = true ]; 108 | string URL = 4; 109 | int64 SKU = 5; 110 | } 111 | 112 | message Value2Pointer { 113 | option (transformer.go_struct) = "Value2Pointer"; 114 | 115 | // In message.pb.go Address field will be of type Address. 116 | Address address_nil = 1 [ (gogoproto.nullable) = false ]; 117 | } 118 | 119 | message Pointer2Value { 120 | option (transformer.go_struct) = "Pointer2Value"; 121 | 122 | // In message.pb.go Address field will be of type *Address. 123 | Address address_not_nil = 1; 124 | } 125 | 126 | message SkippedMessageOne {} // skip it because it hasn't transformer.go_struct option. 127 | message SkippedMessageTwo {} // skip it because it hasn't transformer.go_struct option. 128 | 129 | message Timer { 130 | option (transformer.go_struct) = "TimeModel"; 131 | 132 | google.protobuf.Timestamp time = 1 [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true, (transformer.map_to) = "TimeTime"]; 133 | google.protobuf.Timestamp ptr_time = 2 [ (gogoproto.nullable) = true, (gogoproto.stdtime) = true, (transformer.map_to) = "PtrTimeTime" ]; 134 | google.protobuf.Timestamp time_to_struct = 3 [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true, (transformer.map_to) = "NullsTime"]; 135 | google.protobuf.Timestamp time_to_struct_ptr = 4 [ (gogoproto.nullable) = true, (gogoproto.stdtime) = true, (transformer.map_to) = "PtrNullsTime" ]; 136 | 137 | google.protobuf.Timestamp time_ptr_to_struct = 5 [ (gogoproto.nullable) = true, (gogoproto.stdtime) = true, (transformer.map_to) = "NullsTime2"]; 138 | google.protobuf.Timestamp time_ptr_to_ptr_struct = 6 [ (gogoproto.nullable) = true, (gogoproto.stdtime) = true, (transformer.map_to) = "PtrNullsTime2" ]; 139 | } 140 | 141 | message Ints { 142 | option (transformer.go_struct) = "IntsModel"; 143 | 144 | int32 int_for_32_value = 1 [ (transformer.map_to) = "IntFor32Value", (transformer.map_as) = "IntFor_32Value" ]; 145 | int64 int_for_64_value = 2 [ (transformer.map_to) = "IntFor64Value", (transformer.map_as) = "IntFor_64Value"]; 146 | int32 int32_value = 3; 147 | int64 int64_value = 4; 148 | int64 string_value = 5; 149 | } 150 | -------------------------------------------------------------------------------- /example/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/example/nulls" 7 | ) 8 | 9 | type ( 10 | Product struct { 11 | ID int `db:"id" json:"id"` 12 | Name string `db:"name" json:"name"` 13 | One string `db:"one" json:"one"` 14 | SecondID string `db:"two" json:"two"` 15 | CustomField string `db:"custom_field" json:"custom_field"` 16 | CustomOneof string `db:"custom_oneof" json:"custom_oneof"` 17 | NotsupportedOneof string `db:"notsupported_oneof" json:"notsupported_oneof"` 18 | } 19 | 20 | Order struct { 21 | ID int 22 | FirstID string 23 | SecondID string 24 | ThirdURL string 25 | } 26 | 27 | Address struct { 28 | ID int 29 | Type string 30 | } 31 | 32 | Customer struct { 33 | ID int 34 | Name string 35 | Addresses []Address 36 | DefaultAddress *Address 37 | BillingAddress Address 38 | MapField1 string 39 | MapField2 string 40 | } 41 | 42 | MyLineItem struct { 43 | ID int 44 | Type string 45 | URL string 46 | SKU int 47 | } 48 | 49 | MyLineItemUsage struct { 50 | List []MyLineItem 51 | Item *MyLineItem 52 | } 53 | 54 | Value2Pointer struct { 55 | AddressNil *Address 56 | } 57 | 58 | Pointer2Value struct { 59 | AddressNotNil Address 60 | } 61 | 62 | // TimeModel is used for testing time-related transformations. 63 | TimeModel struct { 64 | TimeTime time.Time 65 | PtrTimeTime *time.Time 66 | NullsTime nulls.Time 67 | PtrNullsTime *nulls.Time 68 | 69 | NullsTime2 nulls.Time 70 | PtrNullsTime2 *nulls.Time 71 | } 72 | 73 | IntsModel struct { 74 | IntFor64Value int 75 | IntFor32Value int 76 | Int32Value int32 77 | Int64Value int64 78 | StringValue string 79 | } 80 | ) 81 | -------------------------------------------------------------------------------- /example/nulls/nulls.go: -------------------------------------------------------------------------------- 1 | package nulls 2 | 3 | import "time" 4 | 5 | // Time represents custom time type. 6 | type Time struct { 7 | Time time.Time 8 | Valid bool 9 | } 10 | -------------------------------------------------------------------------------- /example/transform/custom_transformer.go: -------------------------------------------------------------------------------- 1 | // NOTE: This file is NOT autogenerated and contains custom transformers, 2 | // which are used in the message_transformer.go 3 | 4 | package transform 5 | 6 | import "github.com/bold-commerce/protoc-gen-struct-transformer/example" 7 | 8 | // PbCustomTypeToStringPtrVal is an example of the custom transformer from Pb to go 9 | func PbCustomTypeToStringPtrVal(src *example.CustomType, opts ...TransformParam) string { 10 | applyOptions(opts...) 11 | 12 | if version == "v2" { 13 | return src.Value 14 | } 15 | 16 | return "" 17 | } 18 | 19 | // StringToPbCustomTypeValPtr is an example of the custom transformer from go to Pb 20 | func StringToPbCustomTypeValPtr(src string, opts ...TransformParam) *example.CustomType { 21 | applyOptions(opts...) 22 | 23 | if version == "v2" { 24 | return &example.CustomType{ 25 | Value: src, 26 | } 27 | } 28 | 29 | return nil 30 | } 31 | 32 | // PbCustomOneofToStringPtrVal is an example of the custom transformer from Pb to go for the object with oneof in it 33 | func PbCustomOneofToStringPtrVal(src *example.CustomOneof, opts ...TransformParam) string { 34 | applyOptions(opts...) 35 | 36 | if version == "v2" { 37 | return src.GetStringValue() 38 | } 39 | 40 | return "" 41 | } 42 | 43 | // StringToPbCustomOneofValPtr is an example of the custom transformer from go to Pb for the object with oneof in it 44 | func StringToPbCustomOneofValPtr(src string, opts ...TransformParam) *example.CustomOneof { 45 | applyOptions(opts...) 46 | 47 | if version == "v2" { 48 | return &example.CustomOneof{ 49 | Value: &example.CustomOneof_StringValue{ 50 | StringValue: src, 51 | }, 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | 58 | // ToPbValPtr is a transformer for a non-supported type 59 | // TODO: We need to rename the method to include a field name or type to it. 60 | // So we can have more than 1 unsupported type in the file. 61 | // Alternatively you can use `custom` attribute to make a custom transformer 62 | // Current implementation is a bug, but it is used as a feature, 63 | // so changing the method name will break backward compatibilty 64 | func ToPbValPtr(src string, opts ...TransformParam) *example.NotSupportedOneOf { 65 | return &example.NotSupportedOneOf{TheDecl: &example.NotSupportedOneOf_StringValue{StringValue: src}} 66 | } 67 | 68 | // PbToPtrVal is a transformer for a non-supported type 69 | // See TODO for ToPbValPtr 70 | func PbToPtrVal(src *example.NotSupportedOneOf, opts ...TransformParam) string { 71 | return src.GetStringValue() 72 | } 73 | -------------------------------------------------------------------------------- /example/transform/message_transformer.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-struct-transformer, version: 1.0.7-dev. DO NOT EDIT. 2 | // source file: example/message.proto 3 | // source package: svc.example 4 | 5 | package transform 6 | 7 | import ( 8 | "strconv" 9 | 10 | "github.com/bold-commerce/protoc-gen-struct-transformer/example" 11 | "github.com/bold-commerce/protoc-gen-struct-transformer/example/helpers" 12 | "github.com/bold-commerce/protoc-gen-struct-transformer/example/model" 13 | ) 14 | 15 | // Oneof: "the_decl" 16 | 17 | // message "TheOne" has no option "transformer.go_struct", skipped... 18 | // Oneof: "the_decl" 19 | 20 | // message "NotSupportedOneOf" has no option "transformer.go_struct", skipped... 21 | // Oneof: "value" 22 | 23 | // message "CustomOneof" has no option "transformer.go_struct", skipped... 24 | // message "CustomType" has no option "transformer.go_struct", skipped... 25 | // field skipped: some_field 26 | // message "SkippedMessageOne" has no option "transformer.go_struct", skipped... 27 | // message "SkippedMessageTwo" has no option "transformer.go_struct", skipped... 28 | func PbToProductPtr(src *example.Product, opts ...TransformParam) *model.Product { 29 | if src == nil { 30 | return nil 31 | } 32 | 33 | d := PbToProduct(*src, opts...) 34 | return &d 35 | } 36 | 37 | func PbToProductPtrList(src []*example.Product, opts ...TransformParam) []*model.Product { 38 | resp := make([]*model.Product, len(src)) 39 | 40 | for i, s := range src { 41 | resp[i] = PbToProductPtr(s, opts...) 42 | } 43 | 44 | return resp 45 | } 46 | 47 | func PbToProductPtrVal(src *example.Product, opts ...TransformParam) model.Product { 48 | if src == nil { 49 | return model.Product{} 50 | } 51 | 52 | return PbToProduct(*src, opts...) 53 | } 54 | 55 | func PbToProductPtrValList(src []*example.Product, opts ...TransformParam) []model.Product { 56 | resp := make([]model.Product, len(src)) 57 | 58 | for i, s := range src { 59 | resp[i] = PbToProduct(*s) 60 | } 61 | 62 | return resp 63 | } 64 | 65 | // PbToProductList is DEPRECATED. Use PbToProductPtrValList instead. 66 | func PbToProductList(src []*example.Product, opts ...TransformParam) []model.Product { 67 | return PbToProductPtrValList(src) 68 | } 69 | 70 | func PbToProduct(src example.Product, opts ...TransformParam) model.Product { 71 | s := model.Product{ 72 | ID: int(src.Id), 73 | Name: src.Name, 74 | One: TheOneToString(src.One), 75 | SecondID: TheOneToString(src.SecondId), 76 | CustomField: PbCustomTypeToStringPtrVal(src.CustomField, opts...), 77 | CustomOneof: PbCustomOneofToStringPtrVal(src.CustomOneof, opts...), 78 | NotsupportedOneof: PbToPtrVal(src.NotsupportedOneof, opts...), 79 | } 80 | 81 | applyOptions(opts...) 82 | 83 | return s 84 | } 85 | 86 | func PbToProductValPtr(src example.Product, opts ...TransformParam) *model.Product { 87 | d := PbToProduct(src, opts...) 88 | return &d 89 | } 90 | 91 | func PbToProductValList(src []example.Product, opts ...TransformParam) []model.Product { 92 | resp := make([]model.Product, len(src)) 93 | 94 | for i, s := range src { 95 | resp[i] = PbToProduct(s, opts...) 96 | } 97 | 98 | return resp 99 | } 100 | 101 | func ProductToPbPtr(src *model.Product, opts ...TransformParam) *example.Product { 102 | if src == nil { 103 | return nil 104 | } 105 | 106 | d := ProductToPb(*src, opts...) 107 | return &d 108 | } 109 | 110 | func ProductToPbPtrList(src []*model.Product, opts ...TransformParam) []*example.Product { 111 | resp := make([]*example.Product, len(src)) 112 | 113 | for i, s := range src { 114 | resp[i] = ProductToPbPtr(s, opts...) 115 | } 116 | 117 | return resp 118 | } 119 | 120 | func ProductToPbPtrVal(src *model.Product, opts ...TransformParam) example.Product { 121 | if src == nil { 122 | return example.Product{} 123 | } 124 | 125 | return ProductToPb(*src, opts...) 126 | } 127 | 128 | func ProductToPbValPtrList(src []model.Product, opts ...TransformParam) []*example.Product { 129 | resp := make([]*example.Product, len(src)) 130 | 131 | for i, s := range src { 132 | g := ProductToPb(s, opts...) 133 | resp[i] = &g 134 | } 135 | 136 | return resp 137 | } 138 | 139 | // ProductToPbList is DEPRECATED. Use ProductToPbValPtrList instead. 140 | func ProductToPbList(src []model.Product, opts ...TransformParam) []*example.Product { 141 | return ProductToPbValPtrList(src) 142 | } 143 | 144 | func ProductToPb(src model.Product, opts ...TransformParam) example.Product { 145 | s := example.Product{ 146 | Id: int32(src.ID), 147 | Name: src.Name, 148 | One: &example.TheOne{}, 149 | SecondId: &example.TheOne{}, 150 | CustomField: StringToPbCustomTypeValPtr(src.CustomField, opts...), 151 | CustomOneof: StringToPbCustomOneofValPtr(src.CustomOneof, opts...), 152 | NotsupportedOneof: ToPbValPtr(src.NotsupportedOneof, opts...), 153 | } 154 | 155 | applyOptions(opts...) 156 | 157 | StringToTheOne(src.One, s.One, version) 158 | StringToTheOne(src.SecondID, s.SecondId, version) 159 | 160 | return s 161 | } 162 | 163 | func ProductToPbValPtr(src model.Product, opts ...TransformParam) *example.Product { 164 | d := ProductToPb(src, opts...) 165 | return &d 166 | } 167 | 168 | func ProductToPbValList(src []model.Product, opts ...TransformParam) []example.Product { 169 | resp := make([]example.Product, len(src)) 170 | 171 | for i, s := range src { 172 | resp[i] = ProductToPb(s, opts...) 173 | } 174 | 175 | return resp 176 | } 177 | 178 | func PbToOrderPtr(src *example.Order, opts ...TransformParam) *model.Order { 179 | if src == nil { 180 | return nil 181 | } 182 | 183 | d := PbToOrder(*src, opts...) 184 | return &d 185 | } 186 | 187 | func PbToOrderPtrList(src []*example.Order, opts ...TransformParam) []*model.Order { 188 | resp := make([]*model.Order, len(src)) 189 | 190 | for i, s := range src { 191 | resp[i] = PbToOrderPtr(s, opts...) 192 | } 193 | 194 | return resp 195 | } 196 | 197 | func PbToOrderPtrVal(src *example.Order, opts ...TransformParam) model.Order { 198 | if src == nil { 199 | return model.Order{} 200 | } 201 | 202 | return PbToOrder(*src, opts...) 203 | } 204 | 205 | func PbToOrderPtrValList(src []*example.Order, opts ...TransformParam) []model.Order { 206 | resp := make([]model.Order, len(src)) 207 | 208 | for i, s := range src { 209 | resp[i] = PbToOrder(*s) 210 | } 211 | 212 | return resp 213 | } 214 | 215 | // PbToOrderList is DEPRECATED. Use PbToOrderPtrValList instead. 216 | func PbToOrderList(src []*example.Order, opts ...TransformParam) []model.Order { 217 | return PbToOrderPtrValList(src) 218 | } 219 | 220 | func PbToOrder(src example.Order, opts ...TransformParam) model.Order { 221 | s := model.Order{ 222 | ID: int(src.Id), 223 | FirstID: TheOneToString(src.FirstId), 224 | SecondID: TheOneToString(src.SecondId), 225 | ThirdURL: TheOneToString(src.ThirdUrl), 226 | } 227 | 228 | applyOptions(opts...) 229 | 230 | return s 231 | } 232 | 233 | func PbToOrderValPtr(src example.Order, opts ...TransformParam) *model.Order { 234 | d := PbToOrder(src, opts...) 235 | return &d 236 | } 237 | 238 | func PbToOrderValList(src []example.Order, opts ...TransformParam) []model.Order { 239 | resp := make([]model.Order, len(src)) 240 | 241 | for i, s := range src { 242 | resp[i] = PbToOrder(s, opts...) 243 | } 244 | 245 | return resp 246 | } 247 | 248 | func OrderToPbPtr(src *model.Order, opts ...TransformParam) *example.Order { 249 | if src == nil { 250 | return nil 251 | } 252 | 253 | d := OrderToPb(*src, opts...) 254 | return &d 255 | } 256 | 257 | func OrderToPbPtrList(src []*model.Order, opts ...TransformParam) []*example.Order { 258 | resp := make([]*example.Order, len(src)) 259 | 260 | for i, s := range src { 261 | resp[i] = OrderToPbPtr(s, opts...) 262 | } 263 | 264 | return resp 265 | } 266 | 267 | func OrderToPbPtrVal(src *model.Order, opts ...TransformParam) example.Order { 268 | if src == nil { 269 | return example.Order{} 270 | } 271 | 272 | return OrderToPb(*src, opts...) 273 | } 274 | 275 | func OrderToPbValPtrList(src []model.Order, opts ...TransformParam) []*example.Order { 276 | resp := make([]*example.Order, len(src)) 277 | 278 | for i, s := range src { 279 | g := OrderToPb(s, opts...) 280 | resp[i] = &g 281 | } 282 | 283 | return resp 284 | } 285 | 286 | // OrderToPbList is DEPRECATED. Use OrderToPbValPtrList instead. 287 | func OrderToPbList(src []model.Order, opts ...TransformParam) []*example.Order { 288 | return OrderToPbValPtrList(src) 289 | } 290 | 291 | func OrderToPb(src model.Order, opts ...TransformParam) example.Order { 292 | s := example.Order{ 293 | Id: int64(src.ID), 294 | FirstId: &example.TheOne{}, 295 | SecondId: &example.TheOne{}, 296 | ThirdUrl: &example.TheOne{}, 297 | } 298 | 299 | applyOptions(opts...) 300 | 301 | StringToTheOne(src.FirstID, s.FirstId, version) 302 | StringToTheOne(src.SecondID, s.SecondId, version) 303 | StringToTheOne(src.ThirdURL, s.ThirdUrl, version) 304 | return s 305 | } 306 | 307 | func OrderToPbValPtr(src model.Order, opts ...TransformParam) *example.Order { 308 | d := OrderToPb(src, opts...) 309 | return &d 310 | } 311 | 312 | func OrderToPbValList(src []model.Order, opts ...TransformParam) []example.Order { 313 | resp := make([]example.Order, len(src)) 314 | 315 | for i, s := range src { 316 | resp[i] = OrderToPb(s, opts...) 317 | } 318 | 319 | return resp 320 | } 321 | 322 | func PbToAddressPtr(src *example.Address, opts ...TransformParam) *model.Address { 323 | if src == nil { 324 | return nil 325 | } 326 | 327 | d := PbToAddress(*src, opts...) 328 | return &d 329 | } 330 | 331 | func PbToAddressPtrList(src []*example.Address, opts ...TransformParam) []*model.Address { 332 | resp := make([]*model.Address, len(src)) 333 | 334 | for i, s := range src { 335 | resp[i] = PbToAddressPtr(s, opts...) 336 | } 337 | 338 | return resp 339 | } 340 | 341 | func PbToAddressPtrVal(src *example.Address, opts ...TransformParam) model.Address { 342 | if src == nil { 343 | return model.Address{} 344 | } 345 | 346 | return PbToAddress(*src, opts...) 347 | } 348 | 349 | func PbToAddressPtrValList(src []*example.Address, opts ...TransformParam) []model.Address { 350 | resp := make([]model.Address, len(src)) 351 | 352 | for i, s := range src { 353 | resp[i] = PbToAddress(*s) 354 | } 355 | 356 | return resp 357 | } 358 | 359 | // PbToAddressList is DEPRECATED. Use PbToAddressPtrValList instead. 360 | func PbToAddressList(src []*example.Address, opts ...TransformParam) []model.Address { 361 | return PbToAddressPtrValList(src) 362 | } 363 | 364 | func PbToAddress(src example.Address, opts ...TransformParam) model.Address { 365 | s := model.Address{ 366 | ID: int(src.Id), 367 | Type: src.Type, 368 | } 369 | 370 | applyOptions(opts...) 371 | 372 | return s 373 | } 374 | 375 | func PbToAddressValPtr(src example.Address, opts ...TransformParam) *model.Address { 376 | d := PbToAddress(src, opts...) 377 | return &d 378 | } 379 | 380 | func PbToAddressValList(src []example.Address, opts ...TransformParam) []model.Address { 381 | resp := make([]model.Address, len(src)) 382 | 383 | for i, s := range src { 384 | resp[i] = PbToAddress(s, opts...) 385 | } 386 | 387 | return resp 388 | } 389 | 390 | func AddressToPbPtr(src *model.Address, opts ...TransformParam) *example.Address { 391 | if src == nil { 392 | return nil 393 | } 394 | 395 | d := AddressToPb(*src, opts...) 396 | return &d 397 | } 398 | 399 | func AddressToPbPtrList(src []*model.Address, opts ...TransformParam) []*example.Address { 400 | resp := make([]*example.Address, len(src)) 401 | 402 | for i, s := range src { 403 | resp[i] = AddressToPbPtr(s, opts...) 404 | } 405 | 406 | return resp 407 | } 408 | 409 | func AddressToPbPtrVal(src *model.Address, opts ...TransformParam) example.Address { 410 | if src == nil { 411 | return example.Address{} 412 | } 413 | 414 | return AddressToPb(*src, opts...) 415 | } 416 | 417 | func AddressToPbValPtrList(src []model.Address, opts ...TransformParam) []*example.Address { 418 | resp := make([]*example.Address, len(src)) 419 | 420 | for i, s := range src { 421 | g := AddressToPb(s, opts...) 422 | resp[i] = &g 423 | } 424 | 425 | return resp 426 | } 427 | 428 | // AddressToPbList is DEPRECATED. Use AddressToPbValPtrList instead. 429 | func AddressToPbList(src []model.Address, opts ...TransformParam) []*example.Address { 430 | return AddressToPbValPtrList(src) 431 | } 432 | 433 | func AddressToPb(src model.Address, opts ...TransformParam) example.Address { 434 | s := example.Address{ 435 | Id: int64(src.ID), 436 | Type: src.Type, 437 | } 438 | 439 | applyOptions(opts...) 440 | 441 | return s 442 | } 443 | 444 | func AddressToPbValPtr(src model.Address, opts ...TransformParam) *example.Address { 445 | d := AddressToPb(src, opts...) 446 | return &d 447 | } 448 | 449 | func AddressToPbValList(src []model.Address, opts ...TransformParam) []example.Address { 450 | resp := make([]example.Address, len(src)) 451 | 452 | for i, s := range src { 453 | resp[i] = AddressToPb(s, opts...) 454 | } 455 | 456 | return resp 457 | } 458 | 459 | func PbToCustomerPtr(src *example.Customer, opts ...TransformParam) *model.Customer { 460 | if src == nil { 461 | return nil 462 | } 463 | 464 | d := PbToCustomer(*src, opts...) 465 | return &d 466 | } 467 | 468 | func PbToCustomerPtrList(src []*example.Customer, opts ...TransformParam) []*model.Customer { 469 | resp := make([]*model.Customer, len(src)) 470 | 471 | for i, s := range src { 472 | resp[i] = PbToCustomerPtr(s, opts...) 473 | } 474 | 475 | return resp 476 | } 477 | 478 | func PbToCustomerPtrVal(src *example.Customer, opts ...TransformParam) model.Customer { 479 | if src == nil { 480 | return model.Customer{} 481 | } 482 | 483 | return PbToCustomer(*src, opts...) 484 | } 485 | 486 | func PbToCustomerPtrValList(src []*example.Customer, opts ...TransformParam) []model.Customer { 487 | resp := make([]model.Customer, len(src)) 488 | 489 | for i, s := range src { 490 | resp[i] = PbToCustomer(*s) 491 | } 492 | 493 | return resp 494 | } 495 | 496 | // PbToCustomerList is DEPRECATED. Use PbToCustomerPtrValList instead. 497 | func PbToCustomerList(src []*example.Customer, opts ...TransformParam) []model.Customer { 498 | return PbToCustomerPtrValList(src) 499 | } 500 | 501 | func PbToCustomer(src example.Customer, opts ...TransformParam) model.Customer { 502 | s := model.Customer{ 503 | ID: int(src.Id), 504 | Name: src.Name, 505 | Addresses: PbToAddressPtrValList(src.Addresses, opts...), 506 | DefaultAddress: PbToAddressPtr(src.DefaultAddress, opts...), 507 | BillingAddress: PbToAddress(src.BillingAddress, opts...), 508 | MapField1: src.MapField_1, 509 | MapField2: src.MapFieldToWithoutDigits, 510 | } 511 | 512 | applyOptions(opts...) 513 | 514 | return s 515 | } 516 | 517 | func PbToCustomerValPtr(src example.Customer, opts ...TransformParam) *model.Customer { 518 | d := PbToCustomer(src, opts...) 519 | return &d 520 | } 521 | 522 | func PbToCustomerValList(src []example.Customer, opts ...TransformParam) []model.Customer { 523 | resp := make([]model.Customer, len(src)) 524 | 525 | for i, s := range src { 526 | resp[i] = PbToCustomer(s, opts...) 527 | } 528 | 529 | return resp 530 | } 531 | 532 | func CustomerToPbPtr(src *model.Customer, opts ...TransformParam) *example.Customer { 533 | if src == nil { 534 | return nil 535 | } 536 | 537 | d := CustomerToPb(*src, opts...) 538 | return &d 539 | } 540 | 541 | func CustomerToPbPtrList(src []*model.Customer, opts ...TransformParam) []*example.Customer { 542 | resp := make([]*example.Customer, len(src)) 543 | 544 | for i, s := range src { 545 | resp[i] = CustomerToPbPtr(s, opts...) 546 | } 547 | 548 | return resp 549 | } 550 | 551 | func CustomerToPbPtrVal(src *model.Customer, opts ...TransformParam) example.Customer { 552 | if src == nil { 553 | return example.Customer{} 554 | } 555 | 556 | return CustomerToPb(*src, opts...) 557 | } 558 | 559 | func CustomerToPbValPtrList(src []model.Customer, opts ...TransformParam) []*example.Customer { 560 | resp := make([]*example.Customer, len(src)) 561 | 562 | for i, s := range src { 563 | g := CustomerToPb(s, opts...) 564 | resp[i] = &g 565 | } 566 | 567 | return resp 568 | } 569 | 570 | // CustomerToPbList is DEPRECATED. Use CustomerToPbValPtrList instead. 571 | func CustomerToPbList(src []model.Customer, opts ...TransformParam) []*example.Customer { 572 | return CustomerToPbValPtrList(src) 573 | } 574 | 575 | func CustomerToPb(src model.Customer, opts ...TransformParam) example.Customer { 576 | s := example.Customer{ 577 | Id: int64(src.ID), 578 | Name: src.Name, 579 | Addresses: AddressToPbValPtrList(src.Addresses, opts...), 580 | DefaultAddress: AddressToPbPtr(src.DefaultAddress, opts...), 581 | BillingAddress: AddressToPb(src.BillingAddress, opts...), 582 | MapField_1: src.MapField1, 583 | MapFieldToWithoutDigits: src.MapField2, 584 | } 585 | 586 | applyOptions(opts...) 587 | 588 | return s 589 | } 590 | 591 | func CustomerToPbValPtr(src model.Customer, opts ...TransformParam) *example.Customer { 592 | d := CustomerToPb(src, opts...) 593 | return &d 594 | } 595 | 596 | func CustomerToPbValList(src []model.Customer, opts ...TransformParam) []example.Customer { 597 | resp := make([]example.Customer, len(src)) 598 | 599 | for i, s := range src { 600 | resp[i] = CustomerToPb(s, opts...) 601 | } 602 | 603 | return resp 604 | } 605 | 606 | func PbToMyLineItemUsagePtr(src *example.LineItemUsage, opts ...TransformParam) *model.MyLineItemUsage { 607 | if src == nil { 608 | return nil 609 | } 610 | 611 | d := PbToMyLineItemUsage(*src, opts...) 612 | return &d 613 | } 614 | 615 | func PbToMyLineItemUsagePtrList(src []*example.LineItemUsage, opts ...TransformParam) []*model.MyLineItemUsage { 616 | resp := make([]*model.MyLineItemUsage, len(src)) 617 | 618 | for i, s := range src { 619 | resp[i] = PbToMyLineItemUsagePtr(s, opts...) 620 | } 621 | 622 | return resp 623 | } 624 | 625 | func PbToMyLineItemUsagePtrVal(src *example.LineItemUsage, opts ...TransformParam) model.MyLineItemUsage { 626 | if src == nil { 627 | return model.MyLineItemUsage{} 628 | } 629 | 630 | return PbToMyLineItemUsage(*src, opts...) 631 | } 632 | 633 | func PbToMyLineItemUsagePtrValList(src []*example.LineItemUsage, opts ...TransformParam) []model.MyLineItemUsage { 634 | resp := make([]model.MyLineItemUsage, len(src)) 635 | 636 | for i, s := range src { 637 | resp[i] = PbToMyLineItemUsage(*s) 638 | } 639 | 640 | return resp 641 | } 642 | 643 | // PbToMyLineItemUsageList is DEPRECATED. Use PbToMyLineItemUsagePtrValList instead. 644 | func PbToMyLineItemUsageList(src []*example.LineItemUsage, opts ...TransformParam) []model.MyLineItemUsage { 645 | return PbToMyLineItemUsagePtrValList(src) 646 | } 647 | 648 | func PbToMyLineItemUsage(src example.LineItemUsage, opts ...TransformParam) model.MyLineItemUsage { 649 | s := model.MyLineItemUsage{ 650 | Item: PbToMyLineItemPtr(src.Item, opts...), 651 | List: PbToMyLineItemPtrValList(src.List, opts...), 652 | } 653 | 654 | applyOptions(opts...) 655 | 656 | return s 657 | } 658 | 659 | func PbToMyLineItemUsageValPtr(src example.LineItemUsage, opts ...TransformParam) *model.MyLineItemUsage { 660 | d := PbToMyLineItemUsage(src, opts...) 661 | return &d 662 | } 663 | 664 | func PbToMyLineItemUsageValList(src []example.LineItemUsage, opts ...TransformParam) []model.MyLineItemUsage { 665 | resp := make([]model.MyLineItemUsage, len(src)) 666 | 667 | for i, s := range src { 668 | resp[i] = PbToMyLineItemUsage(s, opts...) 669 | } 670 | 671 | return resp 672 | } 673 | 674 | func MyLineItemUsageToPbPtr(src *model.MyLineItemUsage, opts ...TransformParam) *example.LineItemUsage { 675 | if src == nil { 676 | return nil 677 | } 678 | 679 | d := MyLineItemUsageToPb(*src, opts...) 680 | return &d 681 | } 682 | 683 | func MyLineItemUsageToPbPtrList(src []*model.MyLineItemUsage, opts ...TransformParam) []*example.LineItemUsage { 684 | resp := make([]*example.LineItemUsage, len(src)) 685 | 686 | for i, s := range src { 687 | resp[i] = MyLineItemUsageToPbPtr(s, opts...) 688 | } 689 | 690 | return resp 691 | } 692 | 693 | func MyLineItemUsageToPbPtrVal(src *model.MyLineItemUsage, opts ...TransformParam) example.LineItemUsage { 694 | if src == nil { 695 | return example.LineItemUsage{} 696 | } 697 | 698 | return MyLineItemUsageToPb(*src, opts...) 699 | } 700 | 701 | func MyLineItemUsageToPbValPtrList(src []model.MyLineItemUsage, opts ...TransformParam) []*example.LineItemUsage { 702 | resp := make([]*example.LineItemUsage, len(src)) 703 | 704 | for i, s := range src { 705 | g := MyLineItemUsageToPb(s, opts...) 706 | resp[i] = &g 707 | } 708 | 709 | return resp 710 | } 711 | 712 | // MyLineItemUsageToPbList is DEPRECATED. Use MyLineItemUsageToPbValPtrList instead. 713 | func MyLineItemUsageToPbList(src []model.MyLineItemUsage, opts ...TransformParam) []*example.LineItemUsage { 714 | return MyLineItemUsageToPbValPtrList(src) 715 | } 716 | 717 | func MyLineItemUsageToPb(src model.MyLineItemUsage, opts ...TransformParam) example.LineItemUsage { 718 | s := example.LineItemUsage{ 719 | Item: MyLineItemToPbPtr(src.Item, opts...), 720 | List: MyLineItemToPbValPtrList(src.List, opts...), 721 | } 722 | 723 | applyOptions(opts...) 724 | 725 | return s 726 | } 727 | 728 | func MyLineItemUsageToPbValPtr(src model.MyLineItemUsage, opts ...TransformParam) *example.LineItemUsage { 729 | d := MyLineItemUsageToPb(src, opts...) 730 | return &d 731 | } 732 | 733 | func MyLineItemUsageToPbValList(src []model.MyLineItemUsage, opts ...TransformParam) []example.LineItemUsage { 734 | resp := make([]example.LineItemUsage, len(src)) 735 | 736 | for i, s := range src { 737 | resp[i] = MyLineItemUsageToPb(s, opts...) 738 | } 739 | 740 | return resp 741 | } 742 | 743 | func PbToMyLineItemPtr(src *example.LineItem, opts ...TransformParam) *model.MyLineItem { 744 | if src == nil { 745 | return nil 746 | } 747 | 748 | d := PbToMyLineItem(*src, opts...) 749 | return &d 750 | } 751 | 752 | func PbToMyLineItemPtrList(src []*example.LineItem, opts ...TransformParam) []*model.MyLineItem { 753 | resp := make([]*model.MyLineItem, len(src)) 754 | 755 | for i, s := range src { 756 | resp[i] = PbToMyLineItemPtr(s, opts...) 757 | } 758 | 759 | return resp 760 | } 761 | 762 | func PbToMyLineItemPtrVal(src *example.LineItem, opts ...TransformParam) model.MyLineItem { 763 | if src == nil { 764 | return model.MyLineItem{} 765 | } 766 | 767 | return PbToMyLineItem(*src, opts...) 768 | } 769 | 770 | func PbToMyLineItemPtrValList(src []*example.LineItem, opts ...TransformParam) []model.MyLineItem { 771 | resp := make([]model.MyLineItem, len(src)) 772 | 773 | for i, s := range src { 774 | resp[i] = PbToMyLineItem(*s) 775 | } 776 | 777 | return resp 778 | } 779 | 780 | // PbToMyLineItemList is DEPRECATED. Use PbToMyLineItemPtrValList instead. 781 | func PbToMyLineItemList(src []*example.LineItem, opts ...TransformParam) []model.MyLineItem { 782 | return PbToMyLineItemPtrValList(src) 783 | } 784 | 785 | func PbToMyLineItem(src example.LineItem, opts ...TransformParam) model.MyLineItem { 786 | s := model.MyLineItem{ 787 | ID: int(src.ID), 788 | Type: src.Type, 789 | URL: src.URL, 790 | SKU: int(src.SKU), 791 | } 792 | 793 | applyOptions(opts...) 794 | 795 | return s 796 | } 797 | 798 | func PbToMyLineItemValPtr(src example.LineItem, opts ...TransformParam) *model.MyLineItem { 799 | d := PbToMyLineItem(src, opts...) 800 | return &d 801 | } 802 | 803 | func PbToMyLineItemValList(src []example.LineItem, opts ...TransformParam) []model.MyLineItem { 804 | resp := make([]model.MyLineItem, len(src)) 805 | 806 | for i, s := range src { 807 | resp[i] = PbToMyLineItem(s, opts...) 808 | } 809 | 810 | return resp 811 | } 812 | 813 | func MyLineItemToPbPtr(src *model.MyLineItem, opts ...TransformParam) *example.LineItem { 814 | if src == nil { 815 | return nil 816 | } 817 | 818 | d := MyLineItemToPb(*src, opts...) 819 | return &d 820 | } 821 | 822 | func MyLineItemToPbPtrList(src []*model.MyLineItem, opts ...TransformParam) []*example.LineItem { 823 | resp := make([]*example.LineItem, len(src)) 824 | 825 | for i, s := range src { 826 | resp[i] = MyLineItemToPbPtr(s, opts...) 827 | } 828 | 829 | return resp 830 | } 831 | 832 | func MyLineItemToPbPtrVal(src *model.MyLineItem, opts ...TransformParam) example.LineItem { 833 | if src == nil { 834 | return example.LineItem{} 835 | } 836 | 837 | return MyLineItemToPb(*src, opts...) 838 | } 839 | 840 | func MyLineItemToPbValPtrList(src []model.MyLineItem, opts ...TransformParam) []*example.LineItem { 841 | resp := make([]*example.LineItem, len(src)) 842 | 843 | for i, s := range src { 844 | g := MyLineItemToPb(s, opts...) 845 | resp[i] = &g 846 | } 847 | 848 | return resp 849 | } 850 | 851 | // MyLineItemToPbList is DEPRECATED. Use MyLineItemToPbValPtrList instead. 852 | func MyLineItemToPbList(src []model.MyLineItem, opts ...TransformParam) []*example.LineItem { 853 | return MyLineItemToPbValPtrList(src) 854 | } 855 | 856 | func MyLineItemToPb(src model.MyLineItem, opts ...TransformParam) example.LineItem { 857 | s := example.LineItem{ 858 | ID: int64(src.ID), 859 | Type: src.Type, 860 | URL: src.URL, 861 | SKU: int64(src.SKU), 862 | } 863 | 864 | applyOptions(opts...) 865 | 866 | return s 867 | } 868 | 869 | func MyLineItemToPbValPtr(src model.MyLineItem, opts ...TransformParam) *example.LineItem { 870 | d := MyLineItemToPb(src, opts...) 871 | return &d 872 | } 873 | 874 | func MyLineItemToPbValList(src []model.MyLineItem, opts ...TransformParam) []example.LineItem { 875 | resp := make([]example.LineItem, len(src)) 876 | 877 | for i, s := range src { 878 | resp[i] = MyLineItemToPb(s, opts...) 879 | } 880 | 881 | return resp 882 | } 883 | 884 | func PbToValue2PointerPtr(src *example.Value2Pointer, opts ...TransformParam) *model.Value2Pointer { 885 | if src == nil { 886 | return nil 887 | } 888 | 889 | d := PbToValue2Pointer(*src, opts...) 890 | return &d 891 | } 892 | 893 | func PbToValue2PointerPtrList(src []*example.Value2Pointer, opts ...TransformParam) []*model.Value2Pointer { 894 | resp := make([]*model.Value2Pointer, len(src)) 895 | 896 | for i, s := range src { 897 | resp[i] = PbToValue2PointerPtr(s, opts...) 898 | } 899 | 900 | return resp 901 | } 902 | 903 | func PbToValue2PointerPtrVal(src *example.Value2Pointer, opts ...TransformParam) model.Value2Pointer { 904 | if src == nil { 905 | return model.Value2Pointer{} 906 | } 907 | 908 | return PbToValue2Pointer(*src, opts...) 909 | } 910 | 911 | func PbToValue2PointerPtrValList(src []*example.Value2Pointer, opts ...TransformParam) []model.Value2Pointer { 912 | resp := make([]model.Value2Pointer, len(src)) 913 | 914 | for i, s := range src { 915 | resp[i] = PbToValue2Pointer(*s) 916 | } 917 | 918 | return resp 919 | } 920 | 921 | // PbToValue2PointerList is DEPRECATED. Use PbToValue2PointerPtrValList instead. 922 | func PbToValue2PointerList(src []*example.Value2Pointer, opts ...TransformParam) []model.Value2Pointer { 923 | return PbToValue2PointerPtrValList(src) 924 | } 925 | 926 | func PbToValue2Pointer(src example.Value2Pointer, opts ...TransformParam) model.Value2Pointer { 927 | s := model.Value2Pointer{ 928 | AddressNil: PbToAddressValPtr(src.AddressNil, opts...), 929 | } 930 | 931 | applyOptions(opts...) 932 | 933 | return s 934 | } 935 | 936 | func PbToValue2PointerValPtr(src example.Value2Pointer, opts ...TransformParam) *model.Value2Pointer { 937 | d := PbToValue2Pointer(src, opts...) 938 | return &d 939 | } 940 | 941 | func PbToValue2PointerValList(src []example.Value2Pointer, opts ...TransformParam) []model.Value2Pointer { 942 | resp := make([]model.Value2Pointer, len(src)) 943 | 944 | for i, s := range src { 945 | resp[i] = PbToValue2Pointer(s, opts...) 946 | } 947 | 948 | return resp 949 | } 950 | 951 | func Value2PointerToPbPtr(src *model.Value2Pointer, opts ...TransformParam) *example.Value2Pointer { 952 | if src == nil { 953 | return nil 954 | } 955 | 956 | d := Value2PointerToPb(*src, opts...) 957 | return &d 958 | } 959 | 960 | func Value2PointerToPbPtrList(src []*model.Value2Pointer, opts ...TransformParam) []*example.Value2Pointer { 961 | resp := make([]*example.Value2Pointer, len(src)) 962 | 963 | for i, s := range src { 964 | resp[i] = Value2PointerToPbPtr(s, opts...) 965 | } 966 | 967 | return resp 968 | } 969 | 970 | func Value2PointerToPbPtrVal(src *model.Value2Pointer, opts ...TransformParam) example.Value2Pointer { 971 | if src == nil { 972 | return example.Value2Pointer{} 973 | } 974 | 975 | return Value2PointerToPb(*src, opts...) 976 | } 977 | 978 | func Value2PointerToPbValPtrList(src []model.Value2Pointer, opts ...TransformParam) []*example.Value2Pointer { 979 | resp := make([]*example.Value2Pointer, len(src)) 980 | 981 | for i, s := range src { 982 | g := Value2PointerToPb(s, opts...) 983 | resp[i] = &g 984 | } 985 | 986 | return resp 987 | } 988 | 989 | // Value2PointerToPbList is DEPRECATED. Use Value2PointerToPbValPtrList instead. 990 | func Value2PointerToPbList(src []model.Value2Pointer, opts ...TransformParam) []*example.Value2Pointer { 991 | return Value2PointerToPbValPtrList(src) 992 | } 993 | 994 | func Value2PointerToPb(src model.Value2Pointer, opts ...TransformParam) example.Value2Pointer { 995 | s := example.Value2Pointer{ 996 | AddressNil: AddressToPbPtrVal(src.AddressNil, opts...), 997 | } 998 | 999 | applyOptions(opts...) 1000 | 1001 | return s 1002 | } 1003 | 1004 | func Value2PointerToPbValPtr(src model.Value2Pointer, opts ...TransformParam) *example.Value2Pointer { 1005 | d := Value2PointerToPb(src, opts...) 1006 | return &d 1007 | } 1008 | 1009 | func Value2PointerToPbValList(src []model.Value2Pointer, opts ...TransformParam) []example.Value2Pointer { 1010 | resp := make([]example.Value2Pointer, len(src)) 1011 | 1012 | for i, s := range src { 1013 | resp[i] = Value2PointerToPb(s, opts...) 1014 | } 1015 | 1016 | return resp 1017 | } 1018 | 1019 | func PbToPointer2ValuePtr(src *example.Pointer2Value, opts ...TransformParam) *model.Pointer2Value { 1020 | if src == nil { 1021 | return nil 1022 | } 1023 | 1024 | d := PbToPointer2Value(*src, opts...) 1025 | return &d 1026 | } 1027 | 1028 | func PbToPointer2ValuePtrList(src []*example.Pointer2Value, opts ...TransformParam) []*model.Pointer2Value { 1029 | resp := make([]*model.Pointer2Value, len(src)) 1030 | 1031 | for i, s := range src { 1032 | resp[i] = PbToPointer2ValuePtr(s, opts...) 1033 | } 1034 | 1035 | return resp 1036 | } 1037 | 1038 | func PbToPointer2ValuePtrVal(src *example.Pointer2Value, opts ...TransformParam) model.Pointer2Value { 1039 | if src == nil { 1040 | return model.Pointer2Value{} 1041 | } 1042 | 1043 | return PbToPointer2Value(*src, opts...) 1044 | } 1045 | 1046 | func PbToPointer2ValuePtrValList(src []*example.Pointer2Value, opts ...TransformParam) []model.Pointer2Value { 1047 | resp := make([]model.Pointer2Value, len(src)) 1048 | 1049 | for i, s := range src { 1050 | resp[i] = PbToPointer2Value(*s) 1051 | } 1052 | 1053 | return resp 1054 | } 1055 | 1056 | // PbToPointer2ValueList is DEPRECATED. Use PbToPointer2ValuePtrValList instead. 1057 | func PbToPointer2ValueList(src []*example.Pointer2Value, opts ...TransformParam) []model.Pointer2Value { 1058 | return PbToPointer2ValuePtrValList(src) 1059 | } 1060 | 1061 | func PbToPointer2Value(src example.Pointer2Value, opts ...TransformParam) model.Pointer2Value { 1062 | s := model.Pointer2Value{ 1063 | AddressNotNil: PbToAddressPtrVal(src.AddressNotNil, opts...), 1064 | } 1065 | 1066 | applyOptions(opts...) 1067 | 1068 | return s 1069 | } 1070 | 1071 | func PbToPointer2ValueValPtr(src example.Pointer2Value, opts ...TransformParam) *model.Pointer2Value { 1072 | d := PbToPointer2Value(src, opts...) 1073 | return &d 1074 | } 1075 | 1076 | func PbToPointer2ValueValList(src []example.Pointer2Value, opts ...TransformParam) []model.Pointer2Value { 1077 | resp := make([]model.Pointer2Value, len(src)) 1078 | 1079 | for i, s := range src { 1080 | resp[i] = PbToPointer2Value(s, opts...) 1081 | } 1082 | 1083 | return resp 1084 | } 1085 | 1086 | func Pointer2ValueToPbPtr(src *model.Pointer2Value, opts ...TransformParam) *example.Pointer2Value { 1087 | if src == nil { 1088 | return nil 1089 | } 1090 | 1091 | d := Pointer2ValueToPb(*src, opts...) 1092 | return &d 1093 | } 1094 | 1095 | func Pointer2ValueToPbPtrList(src []*model.Pointer2Value, opts ...TransformParam) []*example.Pointer2Value { 1096 | resp := make([]*example.Pointer2Value, len(src)) 1097 | 1098 | for i, s := range src { 1099 | resp[i] = Pointer2ValueToPbPtr(s, opts...) 1100 | } 1101 | 1102 | return resp 1103 | } 1104 | 1105 | func Pointer2ValueToPbPtrVal(src *model.Pointer2Value, opts ...TransformParam) example.Pointer2Value { 1106 | if src == nil { 1107 | return example.Pointer2Value{} 1108 | } 1109 | 1110 | return Pointer2ValueToPb(*src, opts...) 1111 | } 1112 | 1113 | func Pointer2ValueToPbValPtrList(src []model.Pointer2Value, opts ...TransformParam) []*example.Pointer2Value { 1114 | resp := make([]*example.Pointer2Value, len(src)) 1115 | 1116 | for i, s := range src { 1117 | g := Pointer2ValueToPb(s, opts...) 1118 | resp[i] = &g 1119 | } 1120 | 1121 | return resp 1122 | } 1123 | 1124 | // Pointer2ValueToPbList is DEPRECATED. Use Pointer2ValueToPbValPtrList instead. 1125 | func Pointer2ValueToPbList(src []model.Pointer2Value, opts ...TransformParam) []*example.Pointer2Value { 1126 | return Pointer2ValueToPbValPtrList(src) 1127 | } 1128 | 1129 | func Pointer2ValueToPb(src model.Pointer2Value, opts ...TransformParam) example.Pointer2Value { 1130 | s := example.Pointer2Value{ 1131 | AddressNotNil: AddressToPbValPtr(src.AddressNotNil, opts...), 1132 | } 1133 | 1134 | applyOptions(opts...) 1135 | 1136 | return s 1137 | } 1138 | 1139 | func Pointer2ValueToPbValPtr(src model.Pointer2Value, opts ...TransformParam) *example.Pointer2Value { 1140 | d := Pointer2ValueToPb(src, opts...) 1141 | return &d 1142 | } 1143 | 1144 | func Pointer2ValueToPbValList(src []model.Pointer2Value, opts ...TransformParam) []example.Pointer2Value { 1145 | resp := make([]example.Pointer2Value, len(src)) 1146 | 1147 | for i, s := range src { 1148 | resp[i] = Pointer2ValueToPb(s, opts...) 1149 | } 1150 | 1151 | return resp 1152 | } 1153 | 1154 | func PbToTimeModelPtr(src *example.Timer, opts ...TransformParam) *model.TimeModel { 1155 | if src == nil { 1156 | return nil 1157 | } 1158 | 1159 | d := PbToTimeModel(*src, opts...) 1160 | return &d 1161 | } 1162 | 1163 | func PbToTimeModelPtrList(src []*example.Timer, opts ...TransformParam) []*model.TimeModel { 1164 | resp := make([]*model.TimeModel, len(src)) 1165 | 1166 | for i, s := range src { 1167 | resp[i] = PbToTimeModelPtr(s, opts...) 1168 | } 1169 | 1170 | return resp 1171 | } 1172 | 1173 | func PbToTimeModelPtrVal(src *example.Timer, opts ...TransformParam) model.TimeModel { 1174 | if src == nil { 1175 | return model.TimeModel{} 1176 | } 1177 | 1178 | return PbToTimeModel(*src, opts...) 1179 | } 1180 | 1181 | func PbToTimeModelPtrValList(src []*example.Timer, opts ...TransformParam) []model.TimeModel { 1182 | resp := make([]model.TimeModel, len(src)) 1183 | 1184 | for i, s := range src { 1185 | resp[i] = PbToTimeModel(*s) 1186 | } 1187 | 1188 | return resp 1189 | } 1190 | 1191 | // PbToTimeModelList is DEPRECATED. Use PbToTimeModelPtrValList instead. 1192 | func PbToTimeModelList(src []*example.Timer, opts ...TransformParam) []model.TimeModel { 1193 | return PbToTimeModelPtrValList(src) 1194 | } 1195 | 1196 | func PbToTimeModel(src example.Timer, opts ...TransformParam) model.TimeModel { 1197 | s := model.TimeModel{ 1198 | TimeTime: src.Time, 1199 | PtrTimeTime: src.PtrTime, 1200 | NullsTime: helpers.TimeToNullsTime(src.TimeToStruct), 1201 | PtrNullsTime: helpers.TimePtrToNullsTimePtr(src.TimeToStructPtr), 1202 | NullsTime2: helpers.TimePtrToNullsTime(src.TimePtrToStruct), 1203 | PtrNullsTime2: helpers.TimePtrToNullsTimePtr(src.TimePtrToPtrStruct), 1204 | } 1205 | 1206 | applyOptions(opts...) 1207 | 1208 | return s 1209 | } 1210 | 1211 | func PbToTimeModelValPtr(src example.Timer, opts ...TransformParam) *model.TimeModel { 1212 | d := PbToTimeModel(src, opts...) 1213 | return &d 1214 | } 1215 | 1216 | func PbToTimeModelValList(src []example.Timer, opts ...TransformParam) []model.TimeModel { 1217 | resp := make([]model.TimeModel, len(src)) 1218 | 1219 | for i, s := range src { 1220 | resp[i] = PbToTimeModel(s, opts...) 1221 | } 1222 | 1223 | return resp 1224 | } 1225 | 1226 | func TimeModelToPbPtr(src *model.TimeModel, opts ...TransformParam) *example.Timer { 1227 | if src == nil { 1228 | return nil 1229 | } 1230 | 1231 | d := TimeModelToPb(*src, opts...) 1232 | return &d 1233 | } 1234 | 1235 | func TimeModelToPbPtrList(src []*model.TimeModel, opts ...TransformParam) []*example.Timer { 1236 | resp := make([]*example.Timer, len(src)) 1237 | 1238 | for i, s := range src { 1239 | resp[i] = TimeModelToPbPtr(s, opts...) 1240 | } 1241 | 1242 | return resp 1243 | } 1244 | 1245 | func TimeModelToPbPtrVal(src *model.TimeModel, opts ...TransformParam) example.Timer { 1246 | if src == nil { 1247 | return example.Timer{} 1248 | } 1249 | 1250 | return TimeModelToPb(*src, opts...) 1251 | } 1252 | 1253 | func TimeModelToPbValPtrList(src []model.TimeModel, opts ...TransformParam) []*example.Timer { 1254 | resp := make([]*example.Timer, len(src)) 1255 | 1256 | for i, s := range src { 1257 | g := TimeModelToPb(s, opts...) 1258 | resp[i] = &g 1259 | } 1260 | 1261 | return resp 1262 | } 1263 | 1264 | // TimeModelToPbList is DEPRECATED. Use TimeModelToPbValPtrList instead. 1265 | func TimeModelToPbList(src []model.TimeModel, opts ...TransformParam) []*example.Timer { 1266 | return TimeModelToPbValPtrList(src) 1267 | } 1268 | 1269 | func TimeModelToPb(src model.TimeModel, opts ...TransformParam) example.Timer { 1270 | s := example.Timer{ 1271 | Time: src.TimeTime, 1272 | PtrTime: src.PtrTimeTime, 1273 | TimeToStruct: helpers.NullsTimeToTime(src.NullsTime), 1274 | TimeToStructPtr: helpers.NullsTimePtrToTimePtr(src.PtrNullsTime), 1275 | TimePtrToStruct: helpers.NullsTimeToTimePtr(src.NullsTime2), 1276 | TimePtrToPtrStruct: helpers.NullsTimePtrToTimePtr(src.PtrNullsTime2), 1277 | } 1278 | 1279 | applyOptions(opts...) 1280 | 1281 | return s 1282 | } 1283 | 1284 | func TimeModelToPbValPtr(src model.TimeModel, opts ...TransformParam) *example.Timer { 1285 | d := TimeModelToPb(src, opts...) 1286 | return &d 1287 | } 1288 | 1289 | func TimeModelToPbValList(src []model.TimeModel, opts ...TransformParam) []example.Timer { 1290 | resp := make([]example.Timer, len(src)) 1291 | 1292 | for i, s := range src { 1293 | resp[i] = TimeModelToPb(s, opts...) 1294 | } 1295 | 1296 | return resp 1297 | } 1298 | 1299 | func PbToIntsModelPtr(src *example.Ints, opts ...TransformParam) *model.IntsModel { 1300 | if src == nil { 1301 | return nil 1302 | } 1303 | 1304 | d := PbToIntsModel(*src, opts...) 1305 | return &d 1306 | } 1307 | 1308 | func PbToIntsModelPtrList(src []*example.Ints, opts ...TransformParam) []*model.IntsModel { 1309 | resp := make([]*model.IntsModel, len(src)) 1310 | 1311 | for i, s := range src { 1312 | resp[i] = PbToIntsModelPtr(s, opts...) 1313 | } 1314 | 1315 | return resp 1316 | } 1317 | 1318 | func PbToIntsModelPtrVal(src *example.Ints, opts ...TransformParam) model.IntsModel { 1319 | if src == nil { 1320 | return model.IntsModel{} 1321 | } 1322 | 1323 | return PbToIntsModel(*src, opts...) 1324 | } 1325 | 1326 | func PbToIntsModelPtrValList(src []*example.Ints, opts ...TransformParam) []model.IntsModel { 1327 | resp := make([]model.IntsModel, len(src)) 1328 | 1329 | for i, s := range src { 1330 | resp[i] = PbToIntsModel(*s) 1331 | } 1332 | 1333 | return resp 1334 | } 1335 | 1336 | // PbToIntsModelList is DEPRECATED. Use PbToIntsModelPtrValList instead. 1337 | func PbToIntsModelList(src []*example.Ints, opts ...TransformParam) []model.IntsModel { 1338 | return PbToIntsModelPtrValList(src) 1339 | } 1340 | 1341 | func PbToIntsModel(src example.Ints, opts ...TransformParam) model.IntsModel { 1342 | s := model.IntsModel{ 1343 | IntFor32Value: int(src.IntFor_32Value), 1344 | IntFor64Value: int(src.IntFor_64Value), 1345 | Int32Value: src.Int32Value, 1346 | Int64Value: src.Int64Value, 1347 | StringValue: helpers.Int64ToString(src.StringValue), 1348 | } 1349 | 1350 | applyOptions(opts...) 1351 | 1352 | return s 1353 | } 1354 | 1355 | func PbToIntsModelValPtr(src example.Ints, opts ...TransformParam) *model.IntsModel { 1356 | d := PbToIntsModel(src, opts...) 1357 | return &d 1358 | } 1359 | 1360 | func PbToIntsModelValList(src []example.Ints, opts ...TransformParam) []model.IntsModel { 1361 | resp := make([]model.IntsModel, len(src)) 1362 | 1363 | for i, s := range src { 1364 | resp[i] = PbToIntsModel(s, opts...) 1365 | } 1366 | 1367 | return resp 1368 | } 1369 | 1370 | func IntsModelToPbPtr(src *model.IntsModel, opts ...TransformParam) *example.Ints { 1371 | if src == nil { 1372 | return nil 1373 | } 1374 | 1375 | d := IntsModelToPb(*src, opts...) 1376 | return &d 1377 | } 1378 | 1379 | func IntsModelToPbPtrList(src []*model.IntsModel, opts ...TransformParam) []*example.Ints { 1380 | resp := make([]*example.Ints, len(src)) 1381 | 1382 | for i, s := range src { 1383 | resp[i] = IntsModelToPbPtr(s, opts...) 1384 | } 1385 | 1386 | return resp 1387 | } 1388 | 1389 | func IntsModelToPbPtrVal(src *model.IntsModel, opts ...TransformParam) example.Ints { 1390 | if src == nil { 1391 | return example.Ints{} 1392 | } 1393 | 1394 | return IntsModelToPb(*src, opts...) 1395 | } 1396 | 1397 | func IntsModelToPbValPtrList(src []model.IntsModel, opts ...TransformParam) []*example.Ints { 1398 | resp := make([]*example.Ints, len(src)) 1399 | 1400 | for i, s := range src { 1401 | g := IntsModelToPb(s, opts...) 1402 | resp[i] = &g 1403 | } 1404 | 1405 | return resp 1406 | } 1407 | 1408 | // IntsModelToPbList is DEPRECATED. Use IntsModelToPbValPtrList instead. 1409 | func IntsModelToPbList(src []model.IntsModel, opts ...TransformParam) []*example.Ints { 1410 | return IntsModelToPbValPtrList(src) 1411 | } 1412 | 1413 | func IntsModelToPb(src model.IntsModel, opts ...TransformParam) example.Ints { 1414 | s := example.Ints{ 1415 | IntFor_32Value: int32(src.IntFor32Value), 1416 | IntFor_64Value: int64(src.IntFor64Value), 1417 | Int32Value: src.Int32Value, 1418 | Int64Value: src.Int64Value, 1419 | StringValue: helpers.StringToInt64(src.StringValue), 1420 | } 1421 | 1422 | applyOptions(opts...) 1423 | 1424 | return s 1425 | } 1426 | 1427 | func IntsModelToPbValPtr(src model.IntsModel, opts ...TransformParam) *example.Ints { 1428 | d := IntsModelToPb(src, opts...) 1429 | return &d 1430 | } 1431 | 1432 | func IntsModelToPbValList(src []model.IntsModel, opts ...TransformParam) []example.Ints { 1433 | resp := make([]example.Ints, len(src)) 1434 | 1435 | for i, s := range src { 1436 | resp[i] = IntsModelToPb(s, opts...) 1437 | } 1438 | 1439 | return resp 1440 | } 1441 | 1442 | type OneofTheDecl interface { 1443 | GetStringValue() string 1444 | GetInt64Value() int64 1445 | } 1446 | 1447 | func TheOneToString(src OneofTheDecl) string { 1448 | if s := src.GetStringValue(); s != "" { 1449 | return s 1450 | } 1451 | 1452 | if i := src.GetInt64Value(); i != 0 { 1453 | return strconv.FormatInt(i, 10) 1454 | } 1455 | 1456 | return "" 1457 | } 1458 | 1459 | func StringToTheOne(s string, dst *example.TheOne, v string) { 1460 | i, err := strconv.ParseInt(s, 10, 64) 1461 | if err != nil || v == "v2" { 1462 | dst.TheDecl = &example.TheOne_StringValue{StringValue: s} 1463 | return 1464 | } 1465 | 1466 | dst.TheDecl = &example.TheOne_Int64Value{Int64Value: i} 1467 | return 1468 | } 1469 | -------------------------------------------------------------------------------- /example/transform/options.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-struct-transformer, version: 1.0.7-dev. DO NOT EDIT. 2 | 3 | package transform 4 | 5 | var version string 6 | 7 | // TransformParam is a function option type. 8 | type TransformParam func() 9 | 10 | // WithVersion sets global version variable. 11 | func WithVersion(v string) TransformParam { 12 | return func() { 13 | version = v 14 | } 15 | } 16 | 17 | func applyOptions(opts ...TransformParam) { 18 | for _, o := range opts { 19 | o() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /generator/doc.go: -------------------------------------------------------------------------------- 1 | // Package generator contains functions for generating transform functions. 2 | package generator 3 | -------------------------------------------------------------------------------- /generator/error.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | // ErrNilOptions appears when object has no options. 10 | // 11 | // Options defined as 12 | // 13 | // messages MessageName { 14 | // option (package.message_option_name) = value; 15 | // int64 first_field = 1 [(package.field_option_name = value)]; 16 | // } 17 | ErrNilOptions = errors.New("options are nil") 18 | 19 | // ErrFileSkipped is returned when .proto file has not go_models_file_path 20 | // option. 21 | ErrFileSkipped = errors.New("files was skipped") 22 | ) 23 | 24 | // errOptionNotExists represent option extract-related error. 25 | type errOptionNotExists string 26 | 27 | // newErrOptionNotExists initializes new option-related error. 28 | func newErrOptionNotExists(optName string) error { 29 | return errOptionNotExists(fmt.Sprintf("option %q does not exists", optName)) 30 | } 31 | 32 | // Error is an error interface implementation. 33 | func (e errOptionNotExists) Error() string { 34 | return string(e) 35 | } 36 | 37 | // loggableError is an error type which should be added to result file as a 38 | // comment if happened. 39 | type loggableError struct { 40 | message string 41 | } 42 | 43 | // newLoggableError initializes error which will be added to output file. 44 | func newLoggableError(format string, args ...interface{}) loggableError { 45 | return loggableError{message: fmt.Sprintf(format, args...)} 46 | } 47 | 48 | // Error is an error interface implementation. 49 | func (e loggableError) Error() string { 50 | return e.message 51 | } 52 | -------------------------------------------------------------------------------- /generator/field.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "strings" 8 | 9 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 10 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 11 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 12 | "github.com/iancoleman/strcase" 13 | pkgerrors "github.com/pkg/errors" 14 | ) 15 | 16 | // lastName splits string by "." and returns last part. 17 | func lastName(s string) string { 18 | splt := strings.Split(s, ".") 19 | return splt[len(splt)-1] 20 | } 21 | 22 | // wktgoogleProtobufTimestamp returns *Field created out of 23 | // google.protobuf.Timestamp protobuf field. 24 | func wktgoogleProtobufTimestamp(pname, gname string, gf source.FieldInfo, pnullable bool) *Field { 25 | p2g := "" 26 | g2p := "" 27 | 28 | if gf.Type != "time.Time" { 29 | g := strcase.ToCamel(strings.Replace(gf.Type, ".", "", -1)) 30 | p := "Time" 31 | 32 | if pnullable { 33 | p += "Ptr" 34 | } 35 | 36 | if gf.IsPointer { 37 | g += "Ptr" 38 | } 39 | 40 | p2g = fmt.Sprintf("%sTo%s", p, g) 41 | g2p = fmt.Sprintf("%sTo%s", g, p) 42 | } 43 | 44 | return &Field{ 45 | Name: gname, 46 | ProtoName: pname, 47 | ProtoToGoType: p2g, 48 | GoToProtoType: g2p, 49 | UsePackage: p2g != "", 50 | } 51 | } 52 | 53 | // wktgoogleProtobufString returns *Field created out of 54 | // google.protobuf.StringValue field. 55 | func wktgoogleProtobufString(pname, gname, ftype string) *Field { 56 | g := strcase.ToCamel(strings.Replace(ftype, ".", "", -1)) 57 | p := "StringValue" 58 | 59 | return &Field{ 60 | Name: gname, 61 | ProtoName: pname, 62 | ProtoToGoType: fmt.Sprintf("%sTo%s", p, g), 63 | GoToProtoType: fmt.Sprintf("%sTo%s", g, p), 64 | UsePackage: true, 65 | } 66 | } 67 | 68 | // processSubMessage processes sub messages of current message. Sub message is 69 | // a message type which is used as field type. 70 | // 71 | // In the next example message B is a current message and message A is sub 72 | // message. 73 | // 74 | // message A {} 75 | // message B { A a_field = 1; } 76 | func processSubMessage(w io.Writer, 77 | fdp *descriptor.FieldDescriptorProto, 78 | pname, gname, pbtype string, 79 | mo MessageOption, 80 | goStructFields source.Structure, 81 | customTransformer bool, 82 | ) (*Field, error) { 83 | 84 | if fdp == nil { 85 | return nil, errors.New("input field is nil") 86 | } 87 | 88 | if fdp.Name == nil { 89 | return nil, errors.New("input field name is nil") 90 | } 91 | 92 | tpl := "%sTo%s" 93 | pb := "Pb" 94 | 95 | p2g := "" 96 | g2p := "" 97 | 98 | if mo != nil { 99 | if mo.OneofDecl() != "" && !customTransformer { 100 | pb = strcase.ToCamel(goStructFields[gname].Type) 101 | } else { 102 | pb, pbtype = mo.Target(), pb 103 | } 104 | 105 | pb = strcase.ToCamel(pb) 106 | } 107 | 108 | // custom type converter (methods won't be generated) 109 | if customTransformer { 110 | pb = strcase.ToCamel(goStructFields[gname].Type) 111 | if ln := lastName(pb); strings.Contains(pb, ".") { 112 | pb = strcase.ToCamel(ln) 113 | } 114 | 115 | ptype := lastName(fdp.GetTypeName()) 116 | pbtype = fmt.Sprintf("Pb%s", strcase.ToCamel(ptype)) 117 | } 118 | 119 | if l := fdp.Label; l != nil && *l == descriptor.FieldDescriptorProto_LABEL_REPEATED { 120 | tpl += "List" 121 | if g, ok := goStructFields[gname]; ok { 122 | pb = strcase.ToCamel(g.Type) 123 | } 124 | } 125 | 126 | // embedded fields 127 | fname := gname 128 | if isEmbed := extractEmbedOption(fdp.Options); isEmbed { 129 | // if sub message is embedded use type name as field name. 130 | fname = pb 131 | pb = strcase.ToCamel(pb) 132 | } 133 | 134 | if ln := lastName(pbtype); strings.Contains(pbtype, ".") { 135 | pbtype = strcase.ToCamel(ln) 136 | } 137 | isNullable := extractNullOption(fdp) 138 | 139 | p2g = fmt.Sprintf(tpl, pbtype, pb) 140 | g2p = fmt.Sprintf(tpl, pb, pbtype) 141 | 142 | f := &Field{ 143 | Name: strcase.ToCamel(fname), 144 | ProtoName: strcase.ToCamel(*fdp.Name), 145 | ProtoType: pbtype, 146 | ProtoToGoType: p2g, 147 | GoToProtoType: g2p, 148 | Opts: ", opts...", 149 | ProtoIsPointer: isNullable, 150 | } 151 | 152 | if fm, ok := goStructFields[gname]; ok { 153 | if mo == nil { 154 | return nil, errors.New("mo is nil") 155 | } 156 | f.GoIsPointer = fm.IsPointer 157 | if !customTransformer { 158 | // OneofDecl is used for the BoldCommerce-specific implementation of OneOf for the migration from Int64ToString 159 | f.OneofDecl = mo.OneofDecl() 160 | } 161 | } 162 | 163 | return f, nil 164 | } 165 | 166 | // processSimpleField processes fields of basic types such as int, string and 167 | // so on. 168 | func processSimpleField(w io.Writer, pname, gname string, ftype *descriptor.FieldDescriptorProto_Type, sf source.FieldInfo) (*Field, error) { 169 | 170 | sf.Type = strcase.ToCamel(strings.Replace(sf.Type, ".", "", -1)) // pkg.Type => PkgType 171 | t := types[*ftype] 172 | 173 | // sf: NullsString, pbType: , goType: string, ft: TYPE_STRING, name: Tags, pbaname: Tags 174 | p(w, "// sf: %#v, pbType: %q, goType: %q, ft: %q, pname: %q, gname: %q\n", 175 | sf, t.pbType, t.goType, ftype, pname, gname) 176 | 177 | sft := strings.ToLower(sf.Type) 178 | tpb := strings.ToLower(t.pbType) 179 | tgo := strings.ToLower(t.goType) 180 | 181 | f := &Field{ 182 | Name: gname, 183 | ProtoName: pname, 184 | } 185 | 186 | switch true { 187 | 188 | case (sft == tpb && tpb != "") || (sft == tgo && tpb == ""): // equal types 189 | f.ProtoToGoType = "" 190 | f.GoToProtoType = "" 191 | 192 | case sft != tgo: 193 | p := t.pbType 194 | if p == "" { 195 | p = t.goType 196 | } 197 | 198 | f.ProtoToGoType = fmt.Sprintf("%sTo%s", strcase.ToCamel(p), sf.Type) 199 | f.GoToProtoType = fmt.Sprintf("%sTo%s", sf.Type, strcase.ToCamel(p)) 200 | f.UsePackage = true 201 | 202 | case sft != tpb: 203 | p(w, "// sft: %s, tpb: %s\n", sft, tpb) 204 | f.ProtoToGoType = sft 205 | f.GoToProtoType = tpb 206 | 207 | default: 208 | f.ProtoToGoType = t.pbType 209 | f.GoToProtoType = t.goType 210 | f.UsePackage = t.usePackage 211 | } 212 | 213 | return f, nil 214 | } 215 | 216 | // processField returns filled Field struct for template. 217 | func processField( 218 | w io.Writer, 219 | fdp *descriptor.FieldDescriptorProto, 220 | subMessages MessageOptionList, 221 | goStructFields source.Structure, 222 | ) (*Field, error) { 223 | // If field has transformer.skip == true, it will be not processed. 224 | if skip := extractSkipOption(fdp.Options); skip { 225 | return nil, newLoggableError("field skipped: %s", *fdp.Name) 226 | } 227 | 228 | mapTo, err := getStringOption(fdp.Options, options.E_MapTo) 229 | if _, ok := err.(errOptionNotExists); err != nil && err != ErrNilOptions && !ok { 230 | return nil, pkgerrors.Wrap(err, "mapTo option") 231 | } 232 | 233 | // if field has an options map_as then overwrite fieldName which is pbname 234 | mapAs, err := getStringOption(fdp.Options, options.E_MapAs) 235 | if _, ok := err.(errOptionNotExists); err != nil && err != ErrNilOptions && !ok { 236 | return nil, pkgerrors.Wrap(err, "mapAs option") 237 | } 238 | 239 | pname, gname := prepareFieldNames(*fdp.Name, mapAs, mapTo) 240 | 241 | // check if field exists in destination/Go structure. 242 | gf, ok := goStructFields[gname] 243 | if !ok { 244 | // do not check for embedded fields. 245 | if isEmbed := extractEmbedOption(fdp.Options); !isEmbed { 246 | return nil, pkgerrors.Wrap(errors.New("field not found in destination structure"), gname) 247 | } 248 | } 249 | 250 | p(w, "\n\n// ===============================\n") 251 | if oi := fdp.OneofIndex; oi != nil { 252 | p(w, "// fdp.OneofIndex: %#v\n\n", *oi) 253 | } 254 | 255 | if tn := fdp.TypeName; tn != nil { 256 | p(w, "// fdp.TypeName: %#v\n\n", *tn) 257 | } 258 | if opt := fdp.Options; opt != nil { 259 | p(w, "// fdp.Options: %s\n\n", strings.Replace(fmt.Sprintf("%#v", opt), "\n", "", -1)) 260 | } 261 | p(w, "// fdp.Name: %q, mapAs: %q, mapTo: %q\n", *fdp.Name, mapAs, mapTo) 262 | 263 | // Process subMessages. For details see comments for the TypeName. 264 | if typ := fdp.TypeName; *fdp.Type == descriptor.FieldDescriptorProto_TYPE_MESSAGE && typ != nil { 265 | t := *typ 266 | switch t { 267 | case ".google.protobuf.Timestamp": 268 | isNullable := extractNullOption(fdp) 269 | return wktgoogleProtobufTimestamp(pname, gname, gf, isNullable), nil 270 | case ".google.protobuf.StringValue": 271 | return wktgoogleProtobufString(pname, gname, gf.Type), nil 272 | } 273 | 274 | // if the field has the custom=true - the custom transformer will be used for this field 275 | customTransformer := getBoolOption(fdp.Options, options.E_Custom) 276 | 277 | // Submessage has a name like ".package.type", 1: removes first ".". 278 | mo, _ := subMessages[t[1:]] 279 | // TODO(ekhabarov): pass gf instead of goStructFields 280 | return processSubMessage(w, fdp, pname, gname, t, mo, goStructFields, customTransformer) 281 | } 282 | 283 | return processSimpleField(w, pname, gname, fdp.Type, gf) 284 | } 285 | 286 | // abbreviationUpper checks a incoming string for equality and suffixes, if it 287 | // exists it will be converted to uppercase. 288 | // For instance, identifier fields in models often have a name like SomeID, with 289 | // capitalized "ID", while protobuf auto-generated structures use names like 290 | // "SomeId". 291 | // TODO(ekhabarov): Add cli parameter for such mapping. 292 | func abbreviationUpper(name string) string { 293 | abbreviation := []string{"Id", "Sku", "Url"} 294 | 295 | for _, a := range abbreviation { 296 | if name == a { 297 | return strings.ToUpper(a) 298 | } 299 | 300 | if strings.HasSuffix(name, a) { 301 | return strings.TrimSuffix(name, a) + strings.ToUpper(a) 302 | } 303 | } 304 | 305 | return name 306 | } 307 | 308 | // prepareFieldNames returns names Protobuf and Go for field, considering 309 | // map_to/map_as options and abbreviation rules. 310 | func prepareFieldNames(fname, mapAs, mapTo string) (string, string) { 311 | pname := fname 312 | 313 | if strings.Contains(pname, "_") { 314 | pname = strcase.ToCamel(pname) 315 | } else { 316 | pname = strings.Title(pname) 317 | } 318 | 319 | if mapAs != "" { 320 | pname = mapAs 321 | } 322 | 323 | gname := abbreviationUpper(pname) 324 | if mapTo != "" { 325 | gname = mapTo 326 | } 327 | 328 | return pname, gname 329 | } 330 | -------------------------------------------------------------------------------- /generator/field_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 7 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 8 | "github.com/gogo/protobuf/proto" 9 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | . "github.com/onsi/gomega/gstruct" 13 | pkgerrors "github.com/pkg/errors" 14 | ) 15 | 16 | var _ = Describe("Field", func() { 17 | 18 | Describe("abbreviation", func() { 19 | 20 | Context("when an abbreviation words passed", func() { 21 | 22 | DescribeTable("returns an abbreviation in uppercase", 23 | func(abbr, result string) { 24 | got := abbreviationUpper(abbr) 25 | Expect(got).To(Equal(result)) 26 | }, 27 | 28 | Entry("ID", "Id", "ID"), 29 | Entry("URL", "Url", "URL"), 30 | Entry("SKU", "Sku", "SKU"), 31 | 32 | Entry("NewID", "NewId", "NewID"), 33 | Entry("ProdURL", "ProdUrl", "ProdURL"), 34 | Entry("SKU", "SomeSku", "SomeSKU"), 35 | 36 | Entry("NonAbbr", "NonAbbr", "NonAbbr"), 37 | ) 38 | 39 | }) 40 | }) 41 | 42 | Describe("Well-known types", func() { 43 | 44 | Describe("google.protobuf.Timestamp", func() { 45 | 46 | Context("when fields if of type", func() { 47 | 48 | DescribeTable("check Field stuct", 49 | 50 | func(pname, gname, typ string, gp, pnullable bool, expected Field) { 51 | got := wktgoogleProtobufTimestamp(pname, gname, source.FieldInfo{Type: typ, IsPointer: gp}, pnullable) 52 | 53 | Expect(*got).To(MatchAllFields(Fields{ 54 | "Name": Equal(expected.Name), 55 | "ProtoName": Equal(expected.ProtoName), 56 | "ProtoToGoType": Equal(expected.ProtoToGoType), 57 | "GoToProtoType": Equal(expected.GoToProtoType), 58 | "ProtoType": Equal(expected.ProtoType), 59 | "GoIsPointer": Equal(expected.GoIsPointer), 60 | "ProtoIsPointer": Equal(expected.ProtoIsPointer), 61 | "UsePackage": Equal(expected.UsePackage), 62 | "OneofDecl": Equal(expected.OneofDecl), 63 | "Opts": Equal(expected.Opts), 64 | })) 65 | }, 66 | 67 | Entry("Field not found", "protoName", "name", "AnyGoType", false, false, Field{ 68 | Name: "name", 69 | ProtoName: "protoName", 70 | ProtoToGoType: "TimeToAnyGoType", 71 | GoToProtoType: "AnyGoTypeToTime", 72 | UsePackage: true, 73 | }), 74 | Entry("String", "protoName", "name", "string", false, false, Field{ 75 | Name: "name", 76 | ProtoName: "protoName", 77 | ProtoToGoType: "TimeToString", 78 | GoToProtoType: "StringToTime", 79 | UsePackage: true, 80 | }), 81 | Entry("Time", "protoName", "name", "time.Time", false, false, Field{ 82 | Name: "name", 83 | ProtoName: "protoName", 84 | ProtoToGoType: "", 85 | GoToProtoType: "", 86 | UsePackage: false, 87 | }), 88 | ) 89 | }) 90 | }) 91 | 92 | Describe("google.Protobuf.StringValue", func() { 93 | 94 | Context("when fields if of type", func() { 95 | 96 | DescribeTable("check Field stuct", 97 | 98 | func(pname, gname, ftype string, expected Field) { 99 | got := wktgoogleProtobufString(pname, gname, ftype) 100 | 101 | Expect(*got).To(MatchAllFields(Fields{ 102 | "Name": Equal(expected.Name), 103 | "ProtoName": Equal(expected.ProtoName), 104 | "ProtoToGoType": Equal(expected.ProtoToGoType), 105 | "GoToProtoType": Equal(expected.GoToProtoType), 106 | "ProtoType": Equal(expected.ProtoType), 107 | "GoIsPointer": Equal(expected.GoIsPointer), 108 | "ProtoIsPointer": Equal(expected.ProtoIsPointer), 109 | "UsePackage": Equal(expected.UsePackage), 110 | "OneofDecl": Equal(expected.OneofDecl), 111 | "Opts": Equal(expected.Opts), 112 | })) 113 | }, 114 | 115 | Entry("Field not found", "protoName", "name", "AnyGoType", Field{ 116 | Name: "name", 117 | ProtoName: "protoName", 118 | ProtoToGoType: "StringValueToAnyGoType", 119 | GoToProtoType: "AnyGoTypeToStringValue", 120 | UsePackage: true, 121 | }), 122 | Entry("String", "protoName", "name", "int64", Field{ 123 | Name: "name", 124 | ProtoName: "protoName", 125 | ProtoToGoType: "StringValueToInt64", 126 | GoToProtoType: "Int64ToStringValue", 127 | UsePackage: true, 128 | }), 129 | Entry("pkg.Type", "protoName", "name", "pkg.Type", Field{ 130 | Name: "name", 131 | ProtoName: "protoName", 132 | ProtoToGoType: "StringValueToPkgType", 133 | GoToProtoType: "PkgTypeToStringValue", 134 | UsePackage: true, 135 | }), 136 | ) 137 | }) 138 | }) 139 | }) 140 | 141 | Describe("ProcessSubMessages", func() { 142 | 143 | var ( 144 | protoField = "proto_field" 145 | protoFieldTypeName = "CustomType" 146 | goField = "StringField" 147 | labelRepeated = descriptor.FieldDescriptorProto_LABEL_REPEATED 148 | ) 149 | 150 | DescribeTable("check result", 151 | func(fdp *descriptor.FieldDescriptorProto, pname, gname, pbType string, mo MessageOption, custom bool, expected *Field) { 152 | got, err := processSubMessage(nil, fdp, pname, gname, pbType, mo, goStruct, custom) 153 | Expect(err).NotTo(HaveOccurred()) 154 | 155 | Expect(*got).To(MatchAllFields(Fields{ 156 | "Name": Equal(expected.Name), 157 | "ProtoName": Equal(expected.ProtoName), 158 | "ProtoToGoType": Equal(expected.ProtoToGoType), 159 | "GoToProtoType": Equal(expected.GoToProtoType), 160 | "ProtoType": Equal(expected.ProtoType), 161 | "GoIsPointer": Equal(expected.GoIsPointer), 162 | "ProtoIsPointer": Equal(expected.ProtoIsPointer), 163 | "UsePackage": Equal(expected.UsePackage), 164 | "OneofDecl": Equal(expected.OneofDecl), 165 | "Opts": Equal(expected.Opts), 166 | })) 167 | }, 168 | 169 | Entry("Int64", &descriptor.FieldDescriptorProto{Name: &protoField}, protoField, goField, "int64", mo, false, &Field{ 170 | Name: "StringField", 171 | ProtoName: "ProtoField", 172 | ProtoType: "Pb", 173 | ProtoToGoType: "PbToMoTarget", 174 | GoToProtoType: "MoTargetToPb", 175 | GoIsPointer: false, 176 | ProtoIsPointer: true, 177 | UsePackage: false, 178 | OneofDecl: "", 179 | Opts: ", opts...", 180 | }), 181 | 182 | Entry("Custom field", &descriptor.FieldDescriptorProto{Name: &protoField, TypeName: &protoFieldTypeName}, protoField, goField, "int64", mo, true, &Field{ 183 | Name: "StringField", 184 | ProtoName: "ProtoField", 185 | ProtoType: "PbCustomType", 186 | ProtoToGoType: "PbCustomTypeToString", 187 | GoToProtoType: "StringToPbCustomType", 188 | GoIsPointer: false, 189 | ProtoIsPointer: true, 190 | UsePackage: false, 191 | OneofDecl: "", 192 | Opts: ", opts...", 193 | }), 194 | 195 | Entry("With messageOption and empty oneof", &descriptor.FieldDescriptorProto{Name: &protoField}, protoField, goField, "int64", mo, false, &Field{ 196 | Name: "StringField", 197 | ProtoName: "ProtoField", 198 | ProtoType: "Pb", 199 | ProtoToGoType: "PbToMoTarget", 200 | GoToProtoType: "MoTargetToPb", 201 | GoIsPointer: false, 202 | ProtoIsPointer: true, 203 | UsePackage: false, 204 | OneofDecl: "", 205 | Opts: ", opts...", 206 | }), 207 | 208 | Entry("With messageOption and non-empty oneof", &descriptor.FieldDescriptorProto{Name: &protoField}, protoField, goField, "int64", moWithOneOf, false, &Field{ 209 | Name: "StringField", 210 | ProtoName: "ProtoField", 211 | ProtoType: "int64", 212 | ProtoToGoType: "int64ToString", 213 | GoToProtoType: "StringToint64", 214 | GoIsPointer: false, 215 | ProtoIsPointer: true, 216 | UsePackage: false, 217 | OneofDecl: "oneofField", 218 | Opts: ", opts...", 219 | }), 220 | 221 | Entry("With messageOption, empty oneof, and fqdn type name", 222 | &descriptor.FieldDescriptorProto{ 223 | Name: &protoField, 224 | }, 225 | protoField, goField, "full.type", moWithOneOf, false, 226 | &Field{ 227 | Name: "StringField", 228 | ProtoName: "ProtoField", 229 | ProtoType: "Type", 230 | ProtoToGoType: "TypeToString", 231 | GoToProtoType: "StringToType", 232 | GoIsPointer: false, 233 | ProtoIsPointer: true, 234 | UsePackage: false, 235 | OneofDecl: "oneofField", 236 | Opts: ", opts...", 237 | }), 238 | 239 | Entry("Repeated field", 240 | &descriptor.FieldDescriptorProto{ 241 | Name: &protoField, 242 | Label: &labelRepeated, 243 | }, 244 | protoField, goField, "string", mo, false, 245 | &Field{ 246 | Name: "StringField", 247 | ProtoName: "ProtoField", 248 | ProtoType: "Pb", 249 | ProtoToGoType: "PbToStringList", 250 | GoToProtoType: "StringToPbList", 251 | GoIsPointer: false, 252 | ProtoIsPointer: true, 253 | UsePackage: false, 254 | OneofDecl: "", 255 | Opts: ", opts...", 256 | }), 257 | 258 | Entry("Repeated field when name field found in target struct.", 259 | &descriptor.FieldDescriptorProto{ 260 | Name: &protoField, 261 | Label: &labelRepeated, 262 | }, 263 | protoField, goField, "string", mo, false, 264 | &Field{ 265 | Name: "StringField", 266 | ProtoName: "ProtoField", 267 | ProtoType: "Pb", 268 | ProtoToGoType: "PbToStringList", 269 | GoToProtoType: "StringToPbList", 270 | GoIsPointer: false, 271 | ProtoIsPointer: true, 272 | UsePackage: false, 273 | OneofDecl: "", 274 | Opts: ", opts...", 275 | }), 276 | ) 277 | }) 278 | 279 | Describe("ProcessSimpleField", func() { 280 | 281 | var ( 282 | pint32 = descriptor.FieldDescriptorProto_TYPE_INT32 283 | pint64 = descriptor.FieldDescriptorProto_TYPE_INT64 284 | pstring = descriptor.FieldDescriptorProto_TYPE_STRING 285 | ) 286 | 287 | DescribeTable("check result", 288 | func(pname, gname string, ftype *descriptor.FieldDescriptorProto_Type, sf source.FieldInfo, expected *Field) { 289 | got, err := processSimpleField(nil, pname, gname, ftype, sf) 290 | Expect(err).NotTo(HaveOccurred()) 291 | 292 | Expect(*got).To(MatchAllFields(Fields{ 293 | "Name": Equal(expected.Name), 294 | "ProtoName": Equal(expected.ProtoName), 295 | "ProtoToGoType": Equal(expected.ProtoToGoType), 296 | "GoToProtoType": Equal(expected.GoToProtoType), 297 | "ProtoType": Equal(expected.ProtoType), 298 | "GoIsPointer": Equal(expected.GoIsPointer), 299 | "ProtoIsPointer": Equal(expected.ProtoIsPointer), 300 | "UsePackage": Equal(expected.UsePackage), 301 | "OneofDecl": Equal(expected.OneofDecl), 302 | "Opts": Equal(expected.Opts), 303 | })) 304 | 305 | }, 306 | 307 | Entry("Same types: int64", "Abc", "Abc", &pint64, goStruct["Int64Field"], 308 | &Field{ 309 | Name: "Abc", 310 | ProtoName: "Abc", 311 | ProtoType: "", 312 | ProtoToGoType: "", 313 | GoToProtoType: "", 314 | GoIsPointer: false, 315 | ProtoIsPointer: false, 316 | UsePackage: false, 317 | OneofDecl: "", 318 | Opts: "", 319 | }), 320 | 321 | Entry("Same types: string", "Abc", "Abc", &pstring, goStruct["StringField"], 322 | &Field{ 323 | Name: "Abc", 324 | ProtoName: "Abc", 325 | ProtoType: "", 326 | ProtoToGoType: "", 327 | GoToProtoType: "", 328 | GoIsPointer: false, 329 | ProtoIsPointer: false, 330 | UsePackage: false, 331 | OneofDecl: "", 332 | Opts: "", 333 | }), 334 | 335 | Entry("Similar type: int64 <=> int", "Abc", "Abc", &pint64, goStruct["IntField"], 336 | &Field{ 337 | Name: "Abc", 338 | ProtoName: "Abc", 339 | ProtoType: "", 340 | ProtoToGoType: "int", 341 | GoToProtoType: "int64", 342 | GoIsPointer: false, 343 | ProtoIsPointer: false, 344 | UsePackage: false, 345 | OneofDecl: "", 346 | Opts: "", 347 | }), 348 | 349 | Entry("Different types: int32", "Abc", "Abc", &pint32, goStruct["StringField"], 350 | &Field{ 351 | Name: "Abc", 352 | ProtoName: "Abc", 353 | ProtoType: "", 354 | ProtoToGoType: "Int32ToString", 355 | GoToProtoType: "StringToInt32", 356 | GoIsPointer: false, 357 | ProtoIsPointer: false, 358 | UsePackage: true, 359 | OneofDecl: "", 360 | Opts: "", 361 | }), 362 | 363 | Entry("Different types: int64", "Abc", "Abc", &pint64, goStruct["StringField"], 364 | &Field{ 365 | Name: "Abc", 366 | ProtoName: "Abc", 367 | ProtoType: "", 368 | ProtoToGoType: "Int64ToString", 369 | GoToProtoType: "StringToInt64", 370 | GoIsPointer: false, 371 | ProtoIsPointer: false, 372 | UsePackage: true, 373 | OneofDecl: "", 374 | Opts: "", 375 | }), 376 | 377 | Entry("Custom Go type into basic proto type", "Abc", "Abc", &pstring, goStruct["PkgTypeField"], 378 | &Field{ 379 | Name: "Abc", 380 | ProtoName: "Abc", 381 | ProtoType: "", 382 | ProtoToGoType: "StringToPkgType", 383 | GoToProtoType: "PkgTypeToString", 384 | GoIsPointer: false, 385 | ProtoIsPointer: false, 386 | UsePackage: true, 387 | OneofDecl: "", 388 | Opts: "", 389 | }), 390 | ) 391 | }) 392 | 393 | Describe("prepareFieldNames", func() { 394 | 395 | DescribeTable("parameter combinations", 396 | func(fname, a, t, expectA, expectT string) { 397 | mapAs, mapTo := prepareFieldNames(fname, a, t) 398 | Expect(mapAs).To(Equal(expectA)) 399 | Expect(mapTo).To(Equal(expectT)) 400 | }, 401 | 402 | Entry("Empty mapping", "proto_field_name", "", "", "ProtoFieldName", "ProtoFieldName"), 403 | Entry("Field: ID", "ID", "", "", "ID", "ID"), 404 | Entry("Field: id", "id", "", "", "Id", "ID"), 405 | Entry("Field: Id", "Id", "", "", "Id", "ID"), 406 | Entry("Field: iD", "iD", "", "", "ID", "ID"), 407 | Entry("MapAs without mapTo", "proto_field_name", "map_as", "", "map_as", "map_as"), 408 | Entry("MapTo without mapAs", "proto_field_name", "", "map_to", "ProtoFieldName", "map_to"), 409 | Entry("MapTo and mapAs", "proto_field_name", "map_as", "map_to", "map_as", "map_to"), 410 | ) 411 | 412 | }) 413 | 414 | Describe("processField", func() { 415 | 416 | DescribeTable("check result", 417 | func(f *descriptor.FieldDescriptorProto, skip, embed bool, expected *Field, expectedErr error) { 418 | 419 | err := proto.SetExtension(f.Options, options.E_Skip, bp(skip)) 420 | Expect(err).NotTo(HaveOccurred()) 421 | 422 | err = proto.SetExtension(f.Options, options.E_Embed, bp(embed)) 423 | Expect(err).NotTo(HaveOccurred()) 424 | 425 | field, err := processField(nil, f, subm, goStruct) 426 | if expectedErr == nil { 427 | Expect(err).NotTo(HaveOccurred()) 428 | } else { 429 | // Check just a message here. 430 | Expect(err.Error()).To(Equal(expectedErr.Error())) 431 | } 432 | 433 | if expectedErr == nil { 434 | Expect(*field).To(MatchAllFields(Fields{ 435 | "Name": Equal(expected.Name), 436 | "ProtoName": Equal(expected.ProtoName), 437 | "ProtoToGoType": Equal(expected.ProtoToGoType), 438 | "GoToProtoType": Equal(expected.GoToProtoType), 439 | "ProtoType": Equal(expected.ProtoType), 440 | "GoIsPointer": Equal(expected.GoIsPointer), 441 | "ProtoIsPointer": Equal(expected.ProtoIsPointer), 442 | "UsePackage": Equal(expected.UsePackage), 443 | "OneofDecl": Equal(expected.OneofDecl), 444 | "Opts": Equal(expected.Opts), 445 | })) 446 | } 447 | }, 448 | 449 | Entry("int64", &descriptor.FieldDescriptorProto{ 450 | Name: sp("int64_field"), 451 | TypeName: sp("int64"), 452 | Type: &typInt64, 453 | Options: &descriptor.FieldOptions{}, 454 | }, false, false, &Field{ 455 | Name: "Int64Field", 456 | ProtoName: "Int64Field", 457 | ProtoType: "", 458 | ProtoToGoType: "", 459 | GoToProtoType: "", 460 | GoIsPointer: false, 461 | ProtoIsPointer: false, 462 | UsePackage: false, 463 | OneofDecl: "", 464 | Opts: "", 465 | }, nil), 466 | 467 | Entry("int64: capitalized ID", &descriptor.FieldDescriptorProto{ 468 | Name: sp("ID"), 469 | TypeName: sp("int64"), 470 | Type: &typInt64, 471 | Options: &descriptor.FieldOptions{}, 472 | }, false, false, &Field{ 473 | Name: "ID", 474 | ProtoName: "ID", 475 | ProtoType: "", 476 | ProtoToGoType: "", 477 | GoToProtoType: "", 478 | GoIsPointer: false, 479 | ProtoIsPointer: false, 480 | UsePackage: false, 481 | OneofDecl: "", 482 | Opts: "", 483 | }, nil), 484 | 485 | Entry("int64: id", &descriptor.FieldDescriptorProto{ 486 | Name: sp("id"), 487 | TypeName: sp("int64"), 488 | Type: &typInt64, 489 | Options: &descriptor.FieldOptions{}, 490 | }, false, false, &Field{ 491 | Name: "ID", 492 | ProtoName: "Id", 493 | ProtoType: "", 494 | ProtoToGoType: "", 495 | GoToProtoType: "", 496 | GoIsPointer: false, 497 | ProtoIsPointer: false, 498 | UsePackage: false, 499 | OneofDecl: "", 500 | Opts: "", 501 | }, nil), 502 | 503 | Entry("Skip", &descriptor.FieldDescriptorProto{ 504 | Name: sp("int64_field"), 505 | TypeName: sp("int64"), 506 | Type: &typInt64, 507 | Options: &descriptor.FieldOptions{}, 508 | }, true, false, nil, newLoggableError("field skipped: int64_field")), 509 | 510 | Entry("Target field not found", &descriptor.FieldDescriptorProto{ 511 | Name: sp("not_exists"), 512 | TypeName: sp("int64"), 513 | Type: &typInt64, 514 | Options: &descriptor.FieldOptions{}, 515 | }, false, false, nil, pkgerrors.Wrap(errors.New("field not found in destination structure"), "NotExists")), 516 | 517 | Entry("embed", &descriptor.FieldDescriptorProto{ 518 | Name: sp("PkgTypeField"), 519 | TypeName: sp(".PkgType"), 520 | Type: &typMessage, 521 | Options: &descriptor.FieldOptions{}, 522 | }, false, true, &Field{ 523 | Name: "PkgField", 524 | ProtoName: "PkgTypeField", 525 | ProtoType: "Pb", 526 | ProtoToGoType: "PbToPkgField", 527 | GoToProtoType: "PkgFieldToPb", 528 | GoIsPointer: false, 529 | ProtoIsPointer: true, 530 | UsePackage: false, 531 | OneofDecl: "", 532 | Opts: ", opts...", 533 | }, nil), 534 | 535 | Entry("WKT: Timestamp", &descriptor.FieldDescriptorProto{ 536 | Name: sp("time_field"), 537 | TypeName: sp(".google.protobuf.Timestamp"), 538 | Type: &typMessage, 539 | Options: &descriptor.FieldOptions{}, 540 | }, false, true, &Field{ 541 | Name: "TimeField", 542 | ProtoName: "TimeField", 543 | ProtoType: "", 544 | ProtoToGoType: "", 545 | GoToProtoType: "", 546 | GoIsPointer: false, 547 | ProtoIsPointer: false, 548 | UsePackage: false, 549 | OneofDecl: "", 550 | Opts: "", 551 | }, nil), 552 | 553 | Entry("WKT: StringValue", &descriptor.FieldDescriptorProto{ 554 | Name: sp("string_field"), 555 | TypeName: sp(".google.protobuf.StringValue"), 556 | Type: &typMessage, 557 | Options: &descriptor.FieldOptions{}, 558 | }, false, true, &Field{ 559 | Name: "StringField", 560 | ProtoName: "StringField", 561 | ProtoType: "", 562 | ProtoToGoType: "StringValueToString", 563 | GoToProtoType: "StringToStringValue", 564 | GoIsPointer: false, 565 | ProtoIsPointer: false, 566 | UsePackage: true, 567 | OneofDecl: "", 568 | Opts: "", 569 | }, nil), 570 | ) 571 | 572 | }) 573 | 574 | }) 575 | -------------------------------------------------------------------------------- /generator/file.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 11 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 12 | "github.com/gogo/protobuf/proto" 13 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 14 | plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" 15 | ) 16 | 17 | var ( 18 | // header is a header for each generated files. 19 | header = "// Code generated by protoc-gen-struct-transformer, version: %s. DO NOT EDIT.\n" 20 | 21 | // Next three variables are set by "make install" command and are used as 22 | // version information. See Makefile for details. 23 | version = "" 24 | buildTime = "" 25 | ) 26 | 27 | // WriteStringer exposes two methods: 28 | // Write(p []byte) (n int, err error) 29 | // String() string. 30 | type WriteStringer interface { 31 | io.Writer 32 | fmt.Stringer 33 | } 34 | 35 | // Version returns current generator version. 36 | func Version() string { 37 | return fmt.Sprintf("version: %s\nbuild-time: %s\n", version, buildTime) 38 | } 39 | 40 | // output initializes io.Writer with information about current version. 41 | func output() WriteStringer { 42 | return bytes.NewBufferString(fmt.Sprintf(header, version)) 43 | } 44 | 45 | // fileHeader adds source file/package info into initialized header. 46 | func fileHeader(srcFileName, srcFilePackage, dstPackage string) WriteStringer { 47 | w := output() 48 | 49 | fmt.Fprintln(w, "// source file:", srcFileName) 50 | fmt.Fprintln(w, "// source package:", srcFilePackage) 51 | fmt.Fprintln(w, "\npackage", dstPackage) 52 | 53 | return w 54 | } 55 | 56 | // CollectAllMessages processes all files passed within plugin request to 57 | // collect info about all incoming messages. Generator should have information 58 | // about all messages regardless have those messages transformer options or 59 | // haven't. 60 | func CollectAllMessages(req plugin.CodeGeneratorRequest) (MessageOptionList, error) { 61 | mol := MessageOptionList{} 62 | 63 | for _, f := range req.ProtoFile { 64 | for _, m := range f.MessageType { 65 | structName, _ := extractStructNameOption(m) 66 | 67 | so := messageOption{ 68 | targetName: structName, 69 | } 70 | 71 | if len(m.OneofDecl) > 0 { 72 | hasInt64Value := false 73 | hasStringValue := false 74 | // Check if it implements a specific case of migration from Int64ToString 75 | for _, field := range m.Field { 76 | if field.Name != nil { 77 | if *field.Name == "int64_value" { 78 | hasInt64Value = true 79 | } 80 | if *field.Name == "string_value" { 81 | hasStringValue = true 82 | } 83 | } 84 | } 85 | 86 | int64ToStringOneOf := len(m.Field) == 2 && hasInt64Value && hasStringValue 87 | 88 | if int64ToStringOneOf && len(m.OneofDecl) == 1 { 89 | so.oneofDecl = *m.OneofDecl[0].Name 90 | } 91 | } 92 | 93 | mol[fmt.Sprintf("%s.%s", *f.Package, *m.Name)] = so 94 | } 95 | } 96 | 97 | return mol, nil 98 | } 99 | 100 | // modelsPath returns absolute path to file with models or an error if 101 | // transformer.go_models_file_path option not found. 102 | func modelsPath(m proto.Message) (string, error) { 103 | optionPath, err := getStringOption(m, options.E_GoModelsFilePath) 104 | if err != nil { 105 | return "", ErrFileSkipped 106 | } 107 | 108 | path, err := filepath.Abs(optionPath) 109 | if err != nil { 110 | return "", err 111 | } 112 | 113 | return path, nil 114 | } 115 | 116 | // ProcessFile processes .proto file and returns content as a string. 117 | func ProcessFile(f *descriptor.FileDescriptorProto, packageName, helperPackageName *string, messages MessageOptionList, debug, usePackageInPath bool) (string, string, error) { 118 | path, err := modelsPath(f.Options) 119 | if err != nil { 120 | return "", "", err 121 | } 122 | 123 | structs, err := source.Parse(path, nil) 124 | if err != nil { 125 | return "", "", err 126 | } 127 | 128 | w := fileHeader(*f.Name, *f.Package, *packageName) 129 | 130 | if debug { 131 | p(w, "%s", messages) 132 | } 133 | 134 | repoPackage, err := getStringOption(f.Options, options.E_GoRepoPackage) 135 | if err != nil { 136 | repoPackage = "repo1" 137 | } 138 | 139 | protoPackage, err := getStringOption(f.Options, options.E_GoProtobufPackage) 140 | if err != nil { 141 | protoPackage = "pb1" 142 | } 143 | 144 | var data []*Data 145 | 146 | for _, m := range f.MessageType { 147 | fields, sno, err := processMessage(w, m, messages, structs, debug) 148 | if err != nil { 149 | if e, ok := err.(loggableError); ok { 150 | p(w, "// %s\n", e) 151 | continue 152 | } 153 | return "", "", err 154 | } 155 | 156 | prefixFields(fields, *helperPackageName) 157 | 158 | data = append(data, 159 | &Data{ 160 | Src: m.GetName(), 161 | SrcPref: protoPackage, 162 | SrcFn: "Pb", 163 | SrcPointer: "*", 164 | Dst: sno, 165 | DstPref: repoPackage, 166 | DstFn: sno, 167 | Fields: fields, 168 | }) 169 | } 170 | 171 | if err := execTemplate(w, data); err != nil { 172 | return "", "", err 173 | } 174 | 175 | if err := processOneofFields(w, data); err != nil { 176 | return "", "", err 177 | } 178 | 179 | dir, filename := filepath.Split(*f.Name) 180 | pn := "" 181 | if usePackageInPath { 182 | pn = *packageName 183 | } 184 | absPath := strings.Replace(filepath.Join(dir, pn, filename), ".proto", "_transformer.go", -1) 185 | 186 | return absPath, w.String(), nil 187 | } 188 | 189 | // execTemplate executes main template twice with given data, second pass is 190 | // used for generated reverse functions. 191 | func execTemplate(w io.Writer, data []*Data) error { 192 | for _, d := range data { 193 | t, err := templateWithHelpers("messages") 194 | if err != nil { 195 | return err 196 | } 197 | 198 | if err := t.Execute(w, d); err != nil { 199 | return err 200 | } 201 | 202 | d.swap() 203 | 204 | if err := t.Execute(w, d); err != nil { 205 | return err 206 | } 207 | } 208 | 209 | return nil 210 | } 211 | 212 | // prefixFields adds prefix to fields' convertor functions if prefix is not an 213 | // empty string and field has an attribute UsePackage == true, 214 | func prefixFields(fields []Field, prefix string) { 215 | if prefix == "" { 216 | return 217 | } 218 | 219 | for i, f := range fields { 220 | if !f.UsePackage { 221 | continue 222 | } 223 | fields[i].ProtoToGoType = prefix + "." + f.ProtoToGoType 224 | fields[i].GoToProtoType = prefix + "." + f.GoToProtoType 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /generator/file_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "path/filepath" 7 | "runtime" 8 | "time" 9 | 10 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 11 | "github.com/gogo/protobuf/proto" 12 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 13 | plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" 14 | . "github.com/onsi/ginkgo/v2" 15 | . "github.com/onsi/gomega" 16 | ) 17 | 18 | var _ = Describe("File", func() { 19 | 20 | Describe("File headers", func() { 21 | 22 | Context("when get a header", func() { 23 | 24 | BeforeEach(func() { 25 | version = "v0.0.1" 26 | buildTime = time.Date(2019, time.March, 1, 5, 34, 19, 0, time.UTC).Format(time.RFC3339) 27 | }) 28 | 29 | It("returns version header", func() { 30 | v := Version() 31 | Expect(v).To(Equal("version: v0.0.1\nbuild-time: 2019-03-01T05:34:19Z\n")) 32 | }) 33 | 34 | It("return WriteStringer with header", func() { 35 | o := output() 36 | Expect(o.String()).To(Equal("// Code generated by protoc-gen-struct-transformer, version: v0.0.1. DO NOT EDIT.\n")) 37 | }) 38 | }) 39 | }) 40 | 41 | Describe("CollectAllMessages", func() { 42 | var mt = &descriptor.DescriptorProto{ 43 | Name: sp("message_name"), 44 | Options: &descriptor.MessageOptions{}, 45 | } 46 | 47 | BeforeEach(func() { 48 | err := proto.SetExtension(mt.Options, options.E_GoStruct, sp("go_struct_name")) 49 | Expect(err).NotTo(HaveOccurred()) 50 | }) 51 | 52 | DescribeTable("check code generator request", 53 | func(req plugin.CodeGeneratorRequest, expectexList MessageOptionList) { 54 | mol, err := CollectAllMessages(req) 55 | Expect(err).NotTo(HaveOccurred()) 56 | 57 | if len(expectexList) > 0 { 58 | m := mol["pb.message_name"] 59 | Expect(m).NotTo(BeNil()) 60 | 61 | e := expectexList["message_name"] 62 | Expect(e).NotTo(BeNil()) 63 | 64 | Expect(m.Target()).To(Equal(e.Target())) 65 | Expect(m.Full()).To(Equal(e.Full())) 66 | Expect(m.OneofDecl()).To(Equal(e.OneofDecl())) 67 | } 68 | }, 69 | 70 | Entry("Empty file list", plugin.CodeGeneratorRequest{ 71 | ProtoFile: []*descriptor.FileDescriptorProto{ 72 | &descriptor.FileDescriptorProto{ 73 | Name: sp("protofile"), 74 | Package: sp("pb"), 75 | MessageType: []*descriptor.DescriptorProto{}, 76 | }, 77 | }, 78 | }, map[string]MessageOption{}), 79 | 80 | Entry("Messages without go_struct option", plugin.CodeGeneratorRequest{ 81 | ProtoFile: []*descriptor.FileDescriptorProto{ 82 | &descriptor.FileDescriptorProto{ 83 | Name: sp("protofile"), 84 | Package: sp("pb"), 85 | MessageType: []*descriptor.DescriptorProto{ 86 | {Name: sp("message_name")}, 87 | }, 88 | }, 89 | }, 90 | }, map[string]MessageOption{ 91 | "message_name": messageOption{targetName: "", fullName: "", oneofDecl: ""}, 92 | }), 93 | 94 | Entry("Messages with go_struct option", plugin.CodeGeneratorRequest{ 95 | ProtoFile: []*descriptor.FileDescriptorProto{ 96 | &descriptor.FileDescriptorProto{ 97 | Name: sp("protofile"), 98 | Package: sp("pb"), 99 | MessageType: []*descriptor.DescriptorProto{mt}, 100 | }, 101 | }, 102 | }, map[string]MessageOption{ 103 | "message_name": messageOption{targetName: "go_struct_name", fullName: "", oneofDecl: ""}, 104 | }), 105 | 106 | Entry("Messages with oneOf declaration which does match to int64toString rule", plugin.CodeGeneratorRequest{ 107 | ProtoFile: []*descriptor.FileDescriptorProto{ 108 | &descriptor.FileDescriptorProto{ 109 | Name: sp("protofile"), 110 | Package: sp("pb"), 111 | MessageType: []*descriptor.DescriptorProto{ 112 | { 113 | Name: sp("message_name"), 114 | OneofDecl: []*descriptor.OneofDescriptorProto{ 115 | {Name: sp("oneof_decl_name")}, 116 | }, 117 | Field: []*descriptor.FieldDescriptorProto{ 118 | {Name: sp("int64_value")}, 119 | {Name: sp("string_value")}, 120 | }, 121 | }, 122 | }, 123 | }, 124 | }, 125 | }, map[string]MessageOption{ 126 | "message_name": messageOption{targetName: "", fullName: "", oneofDecl: "oneof_decl_name"}, 127 | }), 128 | 129 | Entry("Messages with oneOf declaration which does not match to int64toString", plugin.CodeGeneratorRequest{ 130 | ProtoFile: []*descriptor.FileDescriptorProto{ 131 | &descriptor.FileDescriptorProto{ 132 | Name: sp("protofile"), 133 | Package: sp("pb"), 134 | MessageType: []*descriptor.DescriptorProto{ 135 | { 136 | Name: sp("message_name"), 137 | OneofDecl: []*descriptor.OneofDescriptorProto{ 138 | {Name: sp("oneof_decl_name")}, 139 | }, 140 | Field: []*descriptor.FieldDescriptorProto{ 141 | {Name: sp("some_field")}, 142 | {Name: sp("some_other_field")}, 143 | }, 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, map[string]MessageOption{ 149 | "message_name": messageOption{targetName: "", fullName: "", oneofDecl: ""}, 150 | }), 151 | ) 152 | }) 153 | 154 | Describe("ProcessFile", func() { 155 | Context("when get a header", func() { 156 | var f *descriptor.FileDescriptorProto 157 | 158 | BeforeEach(func() { 159 | f = &descriptor.FileDescriptorProto{ 160 | Options: &descriptor.FileOptions{}, 161 | Name: sp("product.proto"), 162 | Package: sp("pb"), 163 | MessageType: []*descriptor.DescriptorProto{ 164 | { 165 | Name: sp("Product"), 166 | Field: []*descriptor.FieldDescriptorProto{ 167 | &descriptor.FieldDescriptorProto{ 168 | Name: sp("id"), 169 | Number: nil, 170 | Label: nil, 171 | Type: &typInt64, 172 | TypeName: nil, 173 | Options: &descriptor.FieldOptions{}, 174 | }, 175 | }, 176 | Options: &descriptor.MessageOptions{}, 177 | }, 178 | }, 179 | } 180 | 181 | err := proto.SetExtension(f.Options, options.E_GoModelsFilePath, sp("testdata/model.go")) 182 | Expect(err).NotTo(HaveOccurred()) 183 | 184 | err = proto.SetExtension(f.MessageType[0].Options, options.E_GoStruct, sp("Product")) 185 | Expect(err).NotTo(HaveOccurred()) 186 | }) 187 | 188 | It("returns generated code", func() { 189 | expectedContent, err := ioutil.ReadFile("testdata/processfile.go.golden") 190 | Expect(err).NotTo(HaveOccurred()) 191 | 192 | absPath, content, err := ProcessFile(f, sp("product"), sp("helper-package"), map[string]MessageOption{}, false, false) 193 | Expect(err).NotTo(HaveOccurred()) 194 | Expect(content).To(Equal(string(expectedContent))) 195 | Expect(absPath).To(Equal("product_transformer.go")) 196 | }) 197 | }) 198 | }) 199 | 200 | Describe("modelPath", func() { 201 | 202 | Context("when there is no option go_models_file_path in file", func() { 203 | 204 | It("returns files was skipped error", func() { 205 | p, err := modelsPath(&descriptor.FileOptions{}) 206 | Expect(err).To(MatchError("files was skipped")) 207 | Expect(p).To(Equal("")) 208 | }) 209 | }) 210 | 211 | Context("when file contains go_models_file_path option", func() { 212 | var ( 213 | f *descriptor.FileDescriptorProto 214 | base string 215 | path string 216 | ) 217 | 218 | BeforeEach(func() { 219 | f = &descriptor.FileDescriptorProto{ 220 | Options: &descriptor.FileOptions{}, 221 | } 222 | _, path, _, _ = runtime.Caller(0) 223 | base = filepath.Base(path) 224 | 225 | // set path to current test file as a value for go_models_file_path option 226 | err := proto.SetExtension(f.Options, options.E_GoModelsFilePath, sp(base)) 227 | Expect(err).NotTo(HaveOccurred()) 228 | }) 229 | 230 | It("return abs path for file", func() { 231 | p, err := modelsPath(f.Options) 232 | Expect(err).NotTo(HaveOccurred()) 233 | Expect(p).To(Equal(path)) 234 | }) 235 | }) 236 | }) 237 | 238 | Describe("prefixFields", func() { 239 | 240 | DescribeTable("check returns", 241 | func(pref string, fields, expected []Field) { 242 | prefixFields(fields, pref) 243 | Expect(fields).To(Equal(expected)) 244 | }, 245 | 246 | Entry("One field, do not use package", "pref", 247 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p"}}, 248 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p"}}, 249 | ), 250 | 251 | Entry("One field, use package, empty prefix", "", 252 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p", UsePackage: true}}, 253 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p", UsePackage: true}}, 254 | ), 255 | 256 | Entry("One field, use package", "pref", 257 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p", UsePackage: true}}, 258 | []Field{{ProtoToGoType: "pref.p2g", GoToProtoType: "pref.g2p", UsePackage: true}}, 259 | ), 260 | 261 | Entry("One field, use package", "pref", 262 | []Field{{ProtoToGoType: "p2g", GoToProtoType: "g2p", UsePackage: true}}, 263 | []Field{{ProtoToGoType: "pref.p2g", GoToProtoType: "pref.g2p", UsePackage: true}}, 264 | ), 265 | ) 266 | }) 267 | 268 | Describe("fileHeader", func() { 269 | 270 | BeforeEach(func() { 271 | version = "v1.0.0" 272 | }) 273 | 274 | DescribeTable("check results", 275 | func(f, p, d, expected string) { 276 | ws := fileHeader(f, p, d) 277 | Expect(ws.String()).To(Equal(expected)) 278 | }, 279 | Entry("case 1", "srcfile", "srcpackage", "dstpackage", `// Code generated by protoc-gen-struct-transformer, version: v1.0.0. DO NOT EDIT. 280 | // source file: srcfile 281 | // source package: srcpackage 282 | 283 | package dstpackage 284 | `), 285 | Entry("case 2", "abc", "cde", "fff", `// Code generated by protoc-gen-struct-transformer, version: v1.0.0. DO NOT EDIT. 286 | // source file: abc 287 | // source package: cde 288 | 289 | package fff 290 | `), 291 | ) 292 | }) 293 | 294 | Describe("execTemplate", func() { 295 | 296 | DescribeTable("check results", 297 | func(d []*Data) { 298 | buf := []byte{} 299 | w := bytes.NewBuffer(buf) 300 | 301 | err := execTemplate(w, d) 302 | Expect(err).NotTo(HaveOccurred()) 303 | }, 304 | Entry("", nil), 305 | Entry("", []*Data{ 306 | &Data{ 307 | SrcPref: "src_pref", 308 | Src: "src", 309 | SrcFn: "src_fn", 310 | SrcPointer: "src_pointer", 311 | DstPref: "dst_pref", 312 | Dst: "dst", 313 | DstFn: "dst_fn", 314 | DstPointer: "dst_pointer", 315 | Swapped: false, 316 | HelperPackage: "hp", 317 | Ptr: false, 318 | Fields: []Field{ 319 | { 320 | Name: "FirstField", 321 | ProtoName: "proto_name", 322 | ProtoType: "proto_type", 323 | ProtoToGoType: "FirstProto2go", 324 | GoToProtoType: "FirstGo2proto", 325 | GoIsPointer: false, 326 | ProtoIsPointer: false, 327 | UsePackage: false, 328 | OneofDecl: "", 329 | Opts: "", 330 | }, 331 | { 332 | Name: "SecondField", 333 | ProtoName: "proto_name2", 334 | ProtoType: "proto_type2", 335 | ProtoToGoType: "SecondProto2go", 336 | GoToProtoType: "SecondGo2proto", 337 | GoIsPointer: false, 338 | ProtoIsPointer: false, 339 | UsePackage: false, 340 | OneofDecl: "", 341 | Opts: "", 342 | }, 343 | }, 344 | }, 345 | }), 346 | ) 347 | 348 | }) 349 | 350 | }) 351 | -------------------------------------------------------------------------------- /generator/generator_suite_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 7 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | func TestGenerator(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Generator Suite") 15 | } 16 | 17 | var ( 18 | typInt64 = descriptor.FieldDescriptorProto_TYPE_INT64 19 | typMessage = descriptor.FieldDescriptorProto_TYPE_MESSAGE 20 | 21 | sp = func(s string) *string { 22 | return &s 23 | } 24 | bp = func(b bool) *bool { 25 | return &b 26 | } 27 | 28 | // key - field name, value - field type 29 | // goStruct contains model structure fields. 30 | goStruct = map[string]source.FieldInfo{ 31 | "ID": {Type: "int64"}, 32 | "StringField": {Type: "string"}, 33 | "BoolField": {Type: "bool"}, 34 | "IntField": {Type: "int"}, 35 | "Int32Field": {Type: "int32"}, 36 | "Int64Field": {Type: "int64"}, 37 | "UintField": {Type: "uint"}, 38 | "Uint32Field": {Type: "uint32"}, 39 | "Uint64Field": {Type: "uint64"}, 40 | "Float32Field": {Type: "float32"}, 41 | "Float64Field": {Type: "float64"}, 42 | "TimeField": {Type: "time.Time"}, 43 | "TimePtrField": {Type: "*time.Time"}, 44 | "PkgTypeField": {Type: "pkg.Type"}, 45 | "ProtoField": {Type: "proto.FieldType"}, 46 | 47 | "StringFieldPtr": {Type: "string", IsPointer: true}, 48 | "BoolFieldPtr": {Type: "bool", IsPointer: true}, 49 | "IntFieldPtr": {Type: "int", IsPointer: true}, 50 | "Int32FieldPtr": {Type: "int32", IsPointer: true}, 51 | "Int64FieldPtr": {Type: "int64", IsPointer: true}, 52 | "UintFieldPtr": {Type: "uint", IsPointer: true}, 53 | "Uint32FieldPtr": {Type: "uint32", IsPointer: true}, 54 | "Uint64FieldPtr": {Type: "uint64", IsPointer: true}, 55 | "Float32FieldPtr": {Type: "float32", IsPointer: true}, 56 | "Float64FieldPtr": {Type: "float64", IsPointer: true}, 57 | "TimeFieldPtr": {Type: "time.Time", IsPointer: true}, 58 | "TimePtrFieldPtr": {Type: "*time.Time", IsPointer: true}, 59 | "PkgTypeFieldPtr": {Type: "pkg.Type", IsPointer: true}, 60 | "ProtoFieldPtr": {Type: "proto.FieldType", IsPointer: true}, 61 | } 62 | 63 | mo = messageOption{ 64 | targetName: "moTarget", 65 | fullName: "full.name", 66 | } 67 | moWithOneOf = messageOption{ 68 | targetName: "target", 69 | fullName: "full.name", 70 | oneofDecl: "oneofField", 71 | } 72 | moPkgField = messageOption{ 73 | targetName: "pkgField", 74 | fullName: "full.name", 75 | } 76 | 77 | subm = map[string]MessageOption{ 78 | "FieldName": mo, 79 | "two": moWithOneOf, 80 | "PkgType": moPkgField, 81 | } 82 | ) 83 | -------------------------------------------------------------------------------- /generator/message.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 7 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 8 | ) 9 | 10 | // processMessage processes each message regardless of contains it an options or 11 | // it doesn't. It returns set of fields for template and destination structure 12 | // name extracted from proto message go_struct option. 13 | func processMessage( 14 | w io.Writer, 15 | msg *descriptor.DescriptorProto, 16 | subMessages map[string]MessageOption, 17 | str source.StructureList, 18 | debug bool, 19 | ) ([]Field, string, error) { 20 | 21 | structName, err := extractStructNameOption(msg) 22 | if err != nil { 23 | if msg != nil { 24 | for _, d := range msg.OneofDecl { 25 | p(w, "// Oneof: %#v\n\n", *d.Name) 26 | } 27 | } 28 | 29 | return nil, "", err 30 | } 31 | 32 | tsf, err := source.Lookup(str, structName) 33 | if err != nil { 34 | return nil, "", err 35 | } 36 | 37 | debugWriter := (io.Writer)(nil) 38 | if debug { 39 | debugWriter = w 40 | } 41 | 42 | p(debugWriter, "%s", tsf) 43 | 44 | fields := []Field{} 45 | 46 | for _, f := range msg.Field { 47 | pf, err := processField(debugWriter, f, subMessages, tsf) 48 | if err != nil { 49 | if e, ok := err.(loggableError); ok { 50 | p(w, "// %s\n", e) 51 | continue 52 | } 53 | if err != ErrNilOptions { 54 | return nil, "", err 55 | } 56 | p(w, "// error: %s\n", err) 57 | continue 58 | } 59 | 60 | fields = append(fields, *pf) 61 | } 62 | 63 | return fields, structName, nil 64 | } 65 | -------------------------------------------------------------------------------- /generator/message_options.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "fmt" 4 | 5 | // MessageOption represents protobuf message options. 6 | type MessageOption interface { 7 | // Returns model name from go_struct option. 8 | Target() string 9 | // Full returns FQTN of proto type, such as "google.protobuf.Timestamp" and 10 | // so on. 11 | Full() string 12 | // If true, proto message has not target struct, i.e. go_struct option is 13 | // empty or not found. 14 | Omitted() bool 15 | // Returns Oneof message name. 16 | OneofDecl() string 17 | } 18 | 19 | // MessageOptionList is a list of proto message option. Map key is a message 20 | // name with Full Qualified Type Name (FQTN), format like 21 | // ".google.protobuf.Timestamp". 22 | type MessageOptionList map[string]MessageOption 23 | 24 | func (sol MessageOptionList) String() string { 25 | s := "\n" 26 | for k, v := range sol { 27 | s += fmt.Sprintf("// %q: target: %q, Omitted: %t, OneofDecl: %q\n", 28 | k, v.Target(), v.Omitted(), v.OneofDecl()) 29 | } 30 | 31 | return s 32 | } 33 | 34 | type messageOption struct { 35 | // Value of transformer.go_struct option. 36 | targetName string 37 | // Full message name e.g. svc.example.Address. 38 | fullName string 39 | // OneOf name. 40 | oneofDecl string 41 | } 42 | 43 | func (so messageOption) Target() string { 44 | return so.targetName 45 | } 46 | 47 | func (so messageOption) Full() string { 48 | return so.fullName 49 | } 50 | 51 | func (so messageOption) Omitted() bool { 52 | return so.Target() == "" 53 | } 54 | 55 | func (so messageOption) OneofDecl() string { 56 | return so.oneofDecl 57 | } 58 | -------------------------------------------------------------------------------- /generator/message_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 7 | "github.com/bold-commerce/protoc-gen-struct-transformer/source" 8 | "github.com/gogo/protobuf/proto" 9 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | pkgerrors "github.com/pkg/errors" 13 | ) 14 | 15 | var _ = Describe("Message", func() { 16 | var ( 17 | messagesData = source.StructureList{ 18 | "msg1": goStruct, 19 | } 20 | ) 21 | 22 | Describe("processMessage", func() { 23 | 24 | DescribeTable("check result", 25 | func(msg *descriptor.DescriptorProto, dstStruct string, expFields []Field, expSructName string, expError error) { 26 | if msg != nil && dstStruct != "" { 27 | err := proto.SetExtension(msg.Options, options.E_GoStruct, sp(dstStruct)) 28 | Expect(err).NotTo(HaveOccurred()) 29 | } 30 | 31 | fields, structName, err := processMessage(nil, msg, subm, messagesData, false) 32 | if expError == nil { 33 | Expect(err).NotTo(HaveOccurred()) 34 | } else { 35 | // Check just a message here. 36 | Expect(err.Error()).To(Equal(expError.Error())) 37 | } 38 | 39 | Expect(fields).To(Equal(expFields)) 40 | Expect(structName).To(Equal(expSructName)) 41 | 42 | }, 43 | Entry("Nil message", nil, "", nil, "", newLoggableError("message is nil")), 44 | 45 | Entry("Message without fields", &descriptor.DescriptorProto{ 46 | Name: sp("Msg1"), 47 | Field: nil, 48 | Options: &descriptor.MessageOptions{}, 49 | }, "msg1", []Field{}, "msg1", nil), 50 | 51 | Entry("Message with non_existent field", &descriptor.DescriptorProto{ 52 | Name: sp("Msg1"), 53 | Field: []*descriptor.FieldDescriptorProto{ 54 | &descriptor.FieldDescriptorProto{ 55 | Name: sp("not_exists"), 56 | Number: nil, 57 | Label: nil, 58 | Type: &typInt64, 59 | TypeName: nil, // sub message type 60 | Options: &descriptor.FieldOptions{}, 61 | }, 62 | }, 63 | Options: &descriptor.MessageOptions{}, 64 | }, "msg1", nil, "", pkgerrors.Wrap(errors.New("field not found in destination structure"), "NotExists")), 65 | 66 | Entry("Message with fields", &descriptor.DescriptorProto{ 67 | Name: sp("Msg1"), 68 | Field: []*descriptor.FieldDescriptorProto{ 69 | &descriptor.FieldDescriptorProto{ 70 | Name: sp("int64_field"), 71 | Number: nil, 72 | Label: nil, 73 | Type: &typInt64, 74 | TypeName: nil, // sub message type 75 | Options: &descriptor.FieldOptions{}, 76 | }, 77 | }, 78 | Options: &descriptor.MessageOptions{}, 79 | }, "msg1", []Field{ 80 | { 81 | Name: "Int64Field", 82 | ProtoName: "Int64Field", 83 | ProtoType: "", 84 | ProtoToGoType: "", 85 | GoToProtoType: "", 86 | GoIsPointer: false, 87 | ProtoIsPointer: false, 88 | UsePackage: false, 89 | OneofDecl: "", 90 | Opts: "", 91 | }, 92 | }, "msg1", nil), 93 | 94 | Entry("Message with ID field", &descriptor.DescriptorProto{ 95 | Name: sp("Msg1"), 96 | Field: []*descriptor.FieldDescriptorProto{ 97 | &descriptor.FieldDescriptorProto{ 98 | Name: sp("ID"), 99 | Number: nil, 100 | Label: nil, 101 | Type: &typInt64, 102 | TypeName: nil, // sub message type 103 | Options: &descriptor.FieldOptions{}, 104 | }, 105 | }, 106 | Options: &descriptor.MessageOptions{}, 107 | }, "msg1", []Field{ 108 | { 109 | Name: "ID", 110 | ProtoName: "ID", 111 | ProtoType: "", 112 | ProtoToGoType: "", 113 | GoToProtoType: "", 114 | GoIsPointer: false, 115 | ProtoIsPointer: false, 116 | UsePackage: false, 117 | OneofDecl: "", 118 | Opts: "", 119 | }, 120 | }, "msg1", nil), 121 | ) 122 | }) 123 | 124 | }) 125 | -------------------------------------------------------------------------------- /generator/oneof.go: -------------------------------------------------------------------------------- 1 | // TODO: This is a specific case of OneOf which is used by BoldCommerce and needs to be removed from the plugin. 2 | // This file will be deprecated and removed together with OneOfDecl field once BoldCommerce update their code 3 | 4 | package generator 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "strings" 10 | "text/template" 11 | 12 | "github.com/iancoleman/strcase" 13 | ) 14 | 15 | // processOneofFields adds function for first found field with OneOf 16 | // declaration. 17 | func processOneofFields(w io.Writer, data []*Data) error { 18 | added := map[string]struct{}{} 19 | 20 | for _, d := range data { 21 | if d == nil { 22 | continue 23 | } 24 | d.swap() 25 | 26 | for _, f := range d.Fields { 27 | if !f.IsOneof() { 28 | continue 29 | } 30 | 31 | pt := f.ProtoType 32 | gt := strings.Split(f.GoToProtoType, "To")[0] 33 | 34 | if _, ok := added[gt]; ok { 35 | continue 36 | } 37 | 38 | added[gt] = struct{}{} 39 | 40 | od := OneofData{ 41 | ProtoType: pt, 42 | ProtoPackage: d.SrcPref, 43 | GoType: gt, 44 | Decl: strcase.ToCamel(f.OneofDecl), 45 | OneofDecl: "___decl___", 46 | } 47 | 48 | t, err := template. 49 | New("oneof" + f.ProtoName). 50 | Parse(oneofT) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | if err := t.Execute(w, od); err != nil { 56 | return err 57 | } 58 | // Add oneof function for first found field only. 59 | break 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | // OptHelpers returns file content with optional functions for using options 66 | // with transformations. 67 | func OptHelpers(packageName string) string { 68 | w := output() 69 | fmt.Fprintln(w, "\npackage", packageName) 70 | fmt.Fprintln(w, optionsT) 71 | 72 | return w.String() 73 | } 74 | -------------------------------------------------------------------------------- /generator/oneof_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "time" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("Oneof", func() { 12 | 13 | DescribeTable("processOneofFields", 14 | func(dataList []*Data, expected string) { 15 | buf := []byte{} 16 | w := bytes.NewBuffer(buf) 17 | err := processOneofFields(w, dataList) 18 | Expect(err).NotTo(HaveOccurred()) 19 | 20 | Expect(w.String()).To(Equal(expected)) 21 | }, 22 | 23 | Entry("Empty data", nil, ""), 24 | 25 | Entry("Empty field list", []*Data{ 26 | { 27 | SrcPref: "src_pref", 28 | Src: "src", 29 | SrcFn: "src_fn", 30 | SrcPointer: "src_pointer", 31 | DstPref: "dst_pref", 32 | Dst: "dst", 33 | DstFn: "dst_fn", 34 | DstPointer: "dst_pointer", 35 | Swapped: false, 36 | HelperPackage: "hp", 37 | Ptr: false, 38 | Fields: nil, 39 | }, 40 | }, ""), 41 | 42 | Entry("Single field", []*Data{ 43 | &Data{ 44 | SrcPref: "src_pref", 45 | Src: "src", 46 | SrcFn: "src_fn", 47 | SrcPointer: "src_pointer", 48 | DstPref: "dst_pref", 49 | Dst: "dst", 50 | DstFn: "dst_fn", 51 | DstPointer: "dst_pointer", 52 | Swapped: false, 53 | HelperPackage: "hp", 54 | Ptr: false, 55 | Fields: []Field{ 56 | { 57 | Name: "GoField", 58 | ProtoType: "pt", 59 | GoToProtoType: "gtTopt", 60 | OneofDecl: "decl_name", 61 | }, 62 | }, 63 | }, 64 | }, singleField), 65 | 66 | Entry("Two fields, 2nd is oneof", []*Data{ 67 | &Data{ 68 | SrcPref: "src_pref", 69 | Src: "src", 70 | SrcFn: "src_fn", 71 | SrcPointer: "src_pointer", 72 | DstPref: "dst_pref", 73 | Dst: "dst", 74 | DstFn: "dst_fn", 75 | DstPointer: "dst_pointer", 76 | Swapped: false, 77 | HelperPackage: "hp", 78 | Ptr: false, 79 | Fields: []Field{ 80 | { 81 | Name: "FirstField", 82 | ProtoType: "pt", 83 | GoToProtoType: "gtTopt", 84 | }, 85 | { 86 | Name: "SecondField", 87 | ProtoType: "pt", 88 | GoToProtoType: "gtTopt", 89 | OneofDecl: "decl_name", 90 | }, 91 | }, 92 | }, 93 | }, twoFields), 94 | ) 95 | 96 | BeforeEach(func() { 97 | version = "v1.1.1" 98 | buildTime = time.Date(2019, time.March, 1, 5, 34, 19, 0, time.UTC).Format(time.RFC3339) 99 | }) 100 | 101 | DescribeTable("OptHelpers", 102 | func(name, expected string) { 103 | r := OptHelpers(name) 104 | Expect(r).To(Equal(expected)) 105 | }, 106 | Entry("Package One", "one", headerOne), 107 | ) 108 | 109 | }) 110 | 111 | var ( 112 | singleField = ` 113 | type OneofDeclName interface { 114 | GetStringValue() string 115 | GetInt64Value() int64 116 | } 117 | 118 | func ptTogt(src OneofDeclName) string { 119 | if s := src.GetStringValue(); s != "" { 120 | return s 121 | } 122 | 123 | if i := src.GetInt64Value(); i != 0 { 124 | return strconv.FormatInt(i, 10) 125 | } 126 | 127 | return "" 128 | } 129 | 130 | func gtTopt(s string, dst *dst_pref.pt, v string) { 131 | i, err := strconv.ParseInt(s, 10, 64) 132 | if err != nil || v == "v2"{ 133 | dst.DeclName = &dst_pref.pt_StringValue{StringValue: s} 134 | return 135 | } 136 | 137 | dst.DeclName = &dst_pref.pt_Int64Value{Int64Value: i} 138 | return 139 | } 140 | 141 | ` 142 | 143 | twoFields = ` 144 | type OneofDeclName interface { 145 | GetStringValue() string 146 | GetInt64Value() int64 147 | } 148 | 149 | func ptTogt(src OneofDeclName) string { 150 | if s := src.GetStringValue(); s != "" { 151 | return s 152 | } 153 | 154 | if i := src.GetInt64Value(); i != 0 { 155 | return strconv.FormatInt(i, 10) 156 | } 157 | 158 | return "" 159 | } 160 | 161 | func gtTopt(s string, dst *dst_pref.pt, v string) { 162 | i, err := strconv.ParseInt(s, 10, 64) 163 | if err != nil || v == "v2"{ 164 | dst.DeclName = &dst_pref.pt_StringValue{StringValue: s} 165 | return 166 | } 167 | 168 | dst.DeclName = &dst_pref.pt_Int64Value{Int64Value: i} 169 | return 170 | } 171 | 172 | ` 173 | 174 | headerOne = `// Code generated by protoc-gen-struct-transformer, version: v1.1.1. DO NOT EDIT. 175 | 176 | package one 177 | var version string 178 | 179 | // TransformParam is a function option type. 180 | type TransformParam func() 181 | 182 | // WithVersion sets global version variable. 183 | func WithVersion(v string) TransformParam { 184 | return func() { 185 | version = v 186 | } 187 | } 188 | 189 | func applyOptions(opts ...TransformParam) { 190 | for _, o := range opts { 191 | o() 192 | } 193 | } 194 | 195 | 196 | ` 197 | ) 198 | -------------------------------------------------------------------------------- /generator/option_extractor.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bold-commerce/protoc-gen-struct-transformer/options" 7 | "github.com/gogo/protobuf/gogoproto" 8 | "github.com/gogo/protobuf/proto" 9 | "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 10 | ) 11 | 12 | // extractStructNameOption returns transformer.go_struct option value. 13 | func extractStructNameOption(msg *descriptor.DescriptorProto) (string, error) { 14 | if msg == nil { 15 | return "", newLoggableError("message is nil") 16 | } 17 | 18 | if msg.Options == nil || !proto.HasExtension(msg.Options, options.E_GoStruct) { 19 | return "", newLoggableError("message %q has no option %q, skipped...", *msg.Name, options.E_GoStruct.Name) 20 | } 21 | 22 | ext, err := proto.GetExtension(msg.Options, options.E_GoStruct) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | option, ok := ext.(*string) 28 | if !ok { 29 | return "", fmt.Errorf("extension is %T; want an *string", ext) 30 | } 31 | 32 | return *option, nil 33 | } 34 | 35 | // getStringOption return any option of string type for proto.Message. If 36 | // option exists but has different type, function returns an error. 37 | func getStringOption(m proto.Message, opt *proto.ExtensionDesc) (string, error) { 38 | if m == nil { 39 | return "", ErrNilOptions 40 | } 41 | 42 | if !proto.HasExtension(m, opt) { 43 | return "", newErrOptionNotExists(opt.Name) 44 | } 45 | 46 | ext, err := proto.GetExtension(m, opt) 47 | if err != nil { 48 | return "", err 49 | } 50 | 51 | option, ok := ext.(*string) 52 | if !ok { 53 | return "", fmt.Errorf("extension is %T; want an *string", ext) 54 | } 55 | 56 | return *option, nil 57 | } 58 | 59 | // getBoolOption return any option of bool type for proto.Message. If 60 | // option exists but has different type, function returns false. 61 | func getBoolOption(m proto.Message, opt *proto.ExtensionDesc) bool { 62 | if m == nil { 63 | return false 64 | } 65 | 66 | if !proto.HasExtension(m, opt) { 67 | return false 68 | } 69 | 70 | ext, err := proto.GetExtension(m, opt) 71 | if err != nil { 72 | return false 73 | } 74 | 75 | option, ok := ext.(*bool) 76 | if !ok { 77 | return false 78 | } 79 | 80 | return *option 81 | } 82 | 83 | // extractEmbedOption returns true if proto.Message has an option 84 | // transformer.embed which equals to true. 85 | func extractEmbedOption(m proto.Message) bool { 86 | return getBoolOption(m, options.E_Embed) 87 | } 88 | 89 | // extractSkipOption return value of transformer.skip option or false if 90 | // option does not exist. 91 | func extractSkipOption(m proto.Message) bool { 92 | return getBoolOption(m, options.E_Skip) 93 | } 94 | 95 | // extractNullOption returns true if Field has a gogoproto.nullable option which 96 | // equals to true. 97 | func extractNullOption(f *descriptor.FieldDescriptorProto) bool { 98 | return gogoproto.IsNullable(f) 99 | } 100 | -------------------------------------------------------------------------------- /generator/print.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | // p is a debug function which writes formatted sting into w if w is not nil. 9 | func p(w io.Writer, format string, a ...interface{}) { 10 | if w == nil { 11 | return 12 | } 13 | 14 | fmt.Fprintf(w, format, a...) 15 | } 16 | -------------------------------------------------------------------------------- /generator/request.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Setter is a interface which allows to set map key with value. Both are of 8 | // type string. 9 | type Setter interface { 10 | Set(string, string) error 11 | } 12 | 13 | // SetParameters accepts flag.CommandLine as a setter, string with params 14 | // from protobuf compile input. Functions decodes params and adds it as command 15 | // line flags. 16 | func SetParameters(setter Setter, param *string) error { 17 | if param == nil { 18 | return nil 19 | } 20 | 21 | for _, p := range strings.Split(*param, ",") { 22 | spec := strings.SplitN(p, "=", 2) 23 | // skip output dir and modifiers 24 | if len(spec) == 1 || strings.HasPrefix(spec[0], "M") { 25 | continue 26 | } 27 | 28 | if err := setter.Set(spec[0], spec[1]); err != nil { 29 | return err 30 | } 31 | } 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /generator/request_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | . "github.com/onsi/ginkgo/v2" 7 | . "github.com/onsi/gomega" 8 | ) 9 | 10 | // setter is a faked setter. 11 | type setter map[string]string 12 | 13 | func (s setter) Set(k, v string) error { 14 | if k == "err" { 15 | return errors.New("key_error") 16 | } 17 | s[k] = v 18 | 19 | return nil 20 | } 21 | 22 | func (s setter) String() string { 23 | r := "" 24 | for k, v := range s { 25 | r += fmt.Sprintf("%s=%s,", k, v) 26 | } 27 | 28 | return r 29 | } 30 | 31 | var _ = Describe("Request", func() { 32 | 33 | Describe("SetParameters", func() { 34 | 35 | var set = setter{} 36 | 37 | DescribeTable("check results", 38 | func(s Setter, p *string, expected string) { 39 | err := SetParameters(s, p) 40 | Expect(err).NotTo(HaveOccurred()) 41 | 42 | Expect(s.(fmt.Stringer).String()).To(Equal(expected)) 43 | }, 44 | Entry("nil as param", set, nil, ""), 45 | Entry("", set, sp("Mkey1=val1"), ""), 46 | Entry("", set, sp("Mkey1=val1,Mkey2=val2"), ""), 47 | Entry("", set, sp("Mkey1=val1,Mkey2=val2,key3=val3"), "key3=val3,"), 48 | ) 49 | 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /generator/template.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "text/template" 8 | "text/template/parse" 9 | ) 10 | 11 | func at(t *template.Template) (string, *parse.Tree) { 12 | return t.Name(), t.Tree 13 | } 14 | 15 | func mt(name, tpl string, inc ...*template.Template) *template.Template { 16 | t := template.New(name).Funcs(funcMap) 17 | 18 | for _, v := range inc { 19 | if _, err := t.AddParseTree(at(v)); err != nil { 20 | log.Fatalln("unreachable") 21 | } 22 | } 23 | return template.Must(t.Parse(tpl)) 24 | } 25 | 26 | var ( 27 | funcMap = template.FuncMap{ 28 | "formatField": formatField, 29 | "formatOneofInitField": formatOneofInitField, 30 | } 31 | 32 | funcNameT = mt("FuncName", `{{- .SrcFn }}To{{ .DstFn }}`) 33 | srcParamT = mt("SrcParam", `{{- if .SrcPref }}{{- .SrcPref }}.{{ end }}{{ .Src }}, opts ...TransformParam`) 34 | dstParamT = mt("DstParam", `{{- if .DstPref }}{{- .DstPref }}.{{ end }}{{ .Dst }}`) 35 | ptrValT = mt("PtrValName", `{{- if .Swapped -}} ValPtr {{- else -}} PtrVal {{- end }}`) 36 | ptrT = mt("ptr", `{{ if .Ptr -}} Ptr {{- else -}} Val {{- end }}`) 37 | ptrOnlyT = mt("ptrOnly", `{{ if .Ptr -}} Ptr {{- end }}`) 38 | starT = mt("star", `{{ if .Ptr -}} * {{- end }}`) 39 | 40 | ptr2ptrT = mt("ptr2ptr", `func {{ template "FuncName" . }}Ptr(src *{{ template "SrcParam" . }}) *{{ template "DstParam" . }} { 41 | if src == nil { 42 | return nil 43 | } 44 | 45 | d := {{ template "FuncName" . }}(*src, opts...) 46 | return &d 47 | }`, funcNameT, srcParamT, dstParamT) 48 | 49 | ptr2valT = mt("ptr2val", `func {{ template "FuncName" . }}PtrVal(src *{{ template "SrcParam" . }}) {{ template "DstParam" . }} { 50 | if src == nil { 51 | return {{ template "DstParam" . }}{} 52 | } 53 | 54 | return {{ template "FuncName" . }}(*src, opts...) 55 | }`, funcNameT, srcParamT, dstParamT) 56 | 57 | val2ptrT = mt("val2ptr", `func {{ template "FuncName" . }}ValPtr(src {{ template "SrcParam" . }}) *{{ template "DstParam" . }} { 58 | d := {{ template "FuncName" . }}(src, opts...) 59 | return &d 60 | }`, funcNameT, srcParamT, dstParamT) 61 | 62 | val2valT = mt("val2val", `func {{ template "FuncName" . }}(src {{ template "SrcParam" . }}) {{ template "DstParam" . }} { 63 | s := {{ template "DstParam" . }}{ 64 | {{- with $R := . }} 65 | {{- range $f := .Fields}} 66 | {{ formatField $f $R.Swapped $R.DstPref }} 67 | {{- end -}} 68 | {{- end }} 69 | } 70 | 71 | applyOptions(opts...) 72 | 73 | {{- with $R := . }} 74 | {{ range $f := .Fields }} 75 | {{ formatOneofInitField $f $R.Swapped }} 76 | {{- end -}} 77 | {{- end }} 78 | return s 79 | }`, funcNameT, srcParamT, dstParamT) 80 | 81 | lst2lstT = mt("lst2lst", `func {{ template "FuncName" . }}{{ template "ptr" . }}List(src []{{ template "star" . }}{{ template "SrcParam" . }}) []{{ template "star" . }}{{ template "DstParam" . }} { 82 | resp := make([]{{ template "star" . }}{{ template "DstParam" . }}, len(src)) 83 | 84 | for i, s := range src { 85 | resp[i] = {{ template "FuncName" . }}{{ template "ptrOnly" . }}(s, opts...) 86 | } 87 | 88 | return resp 89 | }`, funcNameT, ptrT, srcParamT, starT, dstParamT, ptrOnlyT) 90 | 91 | ptrlst2ptrlstT = mt("ptrlst2ptrlst", `{{ template "lst2lst" .P true }}`, lst2lstT, funcNameT, ptrT, starT, srcParamT, dstParamT, ptrOnlyT) 92 | 93 | vallst2vallstT = mt("vallst2vallst", `{{ template "lst2lst" . }}`, lst2lstT, funcNameT, ptrT, starT, srcParamT, dstParamT, ptrOnlyT) 94 | 95 | ptrlst2vallstT = mt("ptrlst2vallst", `func {{ template "FuncName" . }}{{ template "PtrValName" . }}List(src []{{ .SrcPointer }}{{ template "SrcParam" . }}) []{{ .DstPointer }}{{ template "DstParam" . }} { 96 | resp := make([]{{ .DstPointer }}{{ template "DstParam" . }}, len(src)) 97 | 98 | for i, s := range src { 99 | {{- if .DstPointer }} 100 | g := {{ template "FuncName" . }}(s, opts...) 101 | resp[i] = &g 102 | {{ else }} 103 | resp[i] = {{ template "FuncName" . }}(*s) 104 | {{ end -}} 105 | } 106 | 107 | return resp 108 | }`, funcNameT, ptrValT, srcParamT, dstParamT) 109 | 110 | ptr2vallstT = mt("ptr2vallst", `// {{ template "FuncName" . }}List is DEPRECATED. Use {{ template "FuncName" . }}{{ template "PtrValName" . }}List instead. 111 | func {{ template "FuncName" . }}List(src []{{ .SrcPointer }}{{ template "SrcParam" . }}) []{{ .DstPointer }}{{ template "DstParam" . }} { 112 | return {{ template "FuncName" . }}{{ template "PtrValName" . }}List(src) 113 | }`, funcNameT, ptrValT, srcParamT, dstParamT) 114 | 115 | tpls = []*template.Template{ 116 | funcNameT, srcParamT, dstParamT, ptrValT, ptrT, ptrOnlyT, starT, ptr2ptrT, 117 | ptr2valT, val2ptrT, val2valT, lst2lstT, ptrlst2ptrlstT, vallst2vallstT, 118 | ptrlst2vallstT, ptr2vallstT, 119 | } 120 | 121 | // Executed with Data struct. 122 | oneFuncitonSetT = `{{- template "ptr2ptr" . }} 123 | 124 | {{ template "ptrlst2ptrlst" . }} 125 | 126 | {{ template "ptr2val" . }} 127 | 128 | {{ template "ptrlst2vallst" . }} 129 | 130 | {{ template "ptr2vallst" . }} 131 | 132 | {{ template "val2val" . }} 133 | 134 | {{ template "val2ptr" . }} 135 | 136 | {{ template "vallst2vallst" . }} 137 | 138 | ` 139 | 140 | oneofT = ` 141 | type Oneof{{ .Decl }} interface { 142 | GetStringValue() string 143 | GetInt64Value() int64 144 | } 145 | 146 | func {{ .ProtoType }}To{{ .GoType }}(src Oneof{{ .Decl }}) string { 147 | if s := src.GetStringValue(); s != "" { 148 | return s 149 | } 150 | 151 | if i := src.GetInt64Value(); i != 0 { 152 | return strconv.FormatInt(i, 10) 153 | } 154 | 155 | return "" 156 | } 157 | 158 | func {{ .GoType }}To{{ .ProtoType }}(s string, dst *{{ .ProtoPackage }}.{{ .ProtoType }}, v string) { 159 | i, err := strconv.ParseInt(s, 10, 64) 160 | if err != nil || v == "v2"{ 161 | dst.{{ .Decl }} = &{{ .ProtoPackage }}.{{ .ProtoType }}_StringValue{StringValue: s} 162 | return 163 | } 164 | 165 | dst.{{ .Decl }} = &{{ .ProtoPackage }}.{{ .ProtoType }}_Int64Value{Int64Value: i} 166 | return 167 | } 168 | 169 | ` 170 | 171 | optionsT = `var version string 172 | 173 | // TransformParam is a function option type. 174 | type TransformParam func() 175 | 176 | // WithVersion sets global version variable. 177 | func WithVersion(v string) TransformParam { 178 | return func() { 179 | version = v 180 | } 181 | } 182 | 183 | func applyOptions(opts ...TransformParam) { 184 | for _, o := range opts { 185 | o() 186 | } 187 | } 188 | 189 | ` 190 | ) 191 | 192 | // templateWithHelpers initializes main oneFuncitonSetT template with given 193 | // name, adds there sub-templates and maps functions into template. 194 | func templateWithHelpers(name string) (*template.Template, error) { 195 | t := template. 196 | New(name). 197 | Funcs(funcMap) 198 | 199 | for _, v := range tpls { 200 | if _, err := t.AddParseTree(at(v)); err != nil { 201 | return nil, err 202 | } 203 | } 204 | 205 | return t.Parse(oneFuncitonSetT) 206 | } 207 | 208 | // Field represents one structure field. 209 | type Field struct { 210 | // Field name in Go structure. 211 | Name string 212 | // Field name in .proto file. 213 | ProtoName string 214 | // Field type in .proto file. 215 | ProtoType string 216 | // Name of function which is used for converting proto field into Go one. 217 | ProtoToGoType string 218 | // Name of function which is used for converting Go field into proto one. 219 | GoToProtoType string 220 | // True if field in model is a pointer. 221 | GoIsPointer bool 222 | // True if field in .proto file has an option gogoproto.nullable = false 223 | ProtoIsPointer bool 224 | // It true, field GoToProtoType and ProtoToGoType functions will be used 225 | // with prefix. 226 | UsePackage bool 227 | // The field has a value when it is used for the oneof migration from Int64 to String for the field 228 | // TODO: This is a specific case of OneOf which is used by BoldCommerce and needs to be removed from the plugin. 229 | // This field will be deprecated together with oneof.go once BoldCommerce update their code 230 | OneofDecl string 231 | Opts string 232 | } 233 | 234 | // IsOneof returns true if Field has non-empty OneOf declaration. 235 | func (f Field) IsOneof() bool { 236 | return f.OneofDecl != "" 237 | } 238 | 239 | // name based on swapped flag return Name or ProtoName for current Field. 240 | func (f Field) name(swapped bool) string { 241 | if swapped { 242 | return f.Name 243 | } 244 | return f.ProtoName 245 | } 246 | 247 | // convertFunc based on swapped flag and value of Field properties returns a type name 248 | // for current Field. 249 | func (f Field) convertFunc(swapped bool) string { 250 | out := f.ProtoToGoType 251 | if swapped { 252 | out = f.GoToProtoType 253 | } 254 | 255 | if f.GoIsPointer && f.ProtoIsPointer { 256 | out += "Ptr" 257 | } 258 | if !f.GoIsPointer && !f.ProtoIsPointer { 259 | out += "" 260 | } 261 | list := strings.HasSuffix(out, "List") 262 | if list { 263 | out = strings.TrimSuffix(out, "List") 264 | } 265 | 266 | suffix := "" 267 | 268 | if !f.GoIsPointer && f.ProtoIsPointer { 269 | if swapped { 270 | suffix = "ValPtr" 271 | } else { 272 | suffix = "PtrVal" 273 | } 274 | } 275 | 276 | if f.GoIsPointer && !f.ProtoIsPointer { 277 | if swapped { 278 | suffix = "PtrVal" 279 | } else { 280 | suffix = "ValPtr" 281 | } 282 | } 283 | 284 | if suffix != "" { 285 | out += suffix 286 | if list { 287 | out += "List" 288 | } 289 | } 290 | 291 | return out 292 | } 293 | 294 | // formatOneofField returns text representation of Oneof field in structure for 295 | // template. 296 | // 297 | // This function is mapped into template. See funcMap variable for details. 298 | func formatOneofField(f Field, swapped bool, pref string) string { 299 | if !f.IsOneof() { 300 | return fmt.Sprintf("/* field %q is not Oneof field*/", f.Name) 301 | } 302 | 303 | out := "src." + f.ProtoName 304 | if swapped { 305 | out = fmt.Sprintf("&%s.%s{}", pref, f.ProtoType) 306 | } else { 307 | if f.ProtoToGoType != "" { 308 | out = fmt.Sprintf("%s(src.%s)", f.ProtoToGoType, f.ProtoName) 309 | } 310 | } 311 | 312 | return out 313 | } 314 | 315 | // formatOneofInitField return text representation for filling up initialized 316 | // oneof fields. 317 | // 318 | // This function is mapped into template. See funcMap variable for details. 319 | func formatOneofInitField(f Field, swapped bool) string { 320 | if !swapped || !f.IsOneof() { 321 | return "" 322 | } 323 | return fmt.Sprintf(" %s(src.%s, s.%s, version)", f.GoToProtoType, f.Name, f.ProtoName) 324 | 325 | } 326 | 327 | func formatComplexField(f Field, swapped bool) string { 328 | if f.ProtoToGoType != "" { 329 | return fmt.Sprintf(" %s(src.%s %s)", f.convertFunc(swapped), f.name(swapped), f.Opts) 330 | } 331 | 332 | return fmt.Sprintf("src.%s", f.name(swapped)) 333 | } 334 | 335 | // formatField returns a string with appropriate field convert functions for 336 | // using in template. 337 | func formatField(f Field, swapped bool, pref string) string { 338 | left := f.name(!swapped) 339 | 340 | right := "" 341 | if f.IsOneof() { 342 | right = formatOneofField(f, swapped, pref) 343 | } else { 344 | right = formatComplexField(f, swapped) 345 | } 346 | 347 | return fmt.Sprintf("%s: %s,", left, right) 348 | } 349 | 350 | // OneofData contains info about OneOf fields. 351 | // 352 | // message TheOne{ <= OneofType 353 | // oneof strint { <= OneofDecl 354 | // string string_value = 1; 355 | // int64 int64_value = 2; 356 | // } 357 | // } 358 | type OneofData struct { 359 | // Package name form proto file. 360 | ProtoPackage string 361 | // Custom data type from proto file which is used for define oneof field. 362 | ProtoType string 363 | // Go destination type. 364 | GoType string 365 | Decl string 366 | OneofDecl string 367 | } 368 | 369 | // Data contains data for fill out template. 370 | type Data struct { 371 | // Prefix for source structure. 372 | SrcPref string 373 | // Source structure name. 374 | Src string 375 | // Left (source) part of transform function name. 376 | SrcFn string 377 | // Contains "*" if source structure is a pointer. 378 | SrcPointer string 379 | // Prefix for destination structure. 380 | DstPref string 381 | // Destination structure name. 382 | Dst string 383 | // Right (destination) part of transform function name. 384 | DstFn string 385 | // Contains "*" if destination structure is a pointer. 386 | DstPointer string 387 | // Field list of structure. 388 | Fields []Field 389 | // If true Fields.GoToProtoType will be used instead of Fileds.ProtoToGoType. 390 | Swapped bool 391 | // Is not empty, package name will be used as prefix for helper functions, 392 | // such as TimeToNullTime, StringToStirnPtr etc. 393 | HelperPackage string 394 | // Ptr is used in template for indication of pointer usage. 395 | Ptr bool 396 | } 397 | 398 | // swap swaps source and destination parameters for using in reverse functions. 399 | func (d *Data) swap() { 400 | d.SrcPref, d.DstPref = d.DstPref, d.SrcPref 401 | d.Src, d.Dst = d.Dst, d.Src 402 | d.SrcFn, d.DstFn = d.DstFn, d.SrcFn 403 | d.SrcPointer, d.DstPointer = d.DstPointer, d.SrcPointer 404 | d.Swapped = !d.Swapped 405 | } 406 | 407 | // P sets Ptr flag of Data structure. Used inside template. Should be exported 408 | // in template case. 409 | func (d Data) P(t bool) Data { 410 | d.Ptr = t 411 | return d 412 | } 413 | -------------------------------------------------------------------------------- /generator/template_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | . "github.com/onsi/ginkgo/v2" 8 | . "github.com/onsi/gomega" 9 | . "github.com/onsi/gomega/gstruct" 10 | ) 11 | 12 | func xEntry(g2p, p2g string, gg, pp, swapped bool, expected string) TableEntry { 13 | args := []interface{}{g2p, p2g, gg, pp, swapped, expected} 14 | 15 | desc := fmt.Sprintf("GoToProtoType: %q, ProtoToGoType: %q, GoIsPointer: %t, ProtoIsPointer: %t, swapped: %t, expected: %q", args...) 16 | return Entry(desc, args...) 17 | } 18 | 19 | var _ = Describe("Template", func() { 20 | 21 | Describe("Field methods", func() { 22 | 23 | var field = &Field{ 24 | Name: "name", 25 | ProtoName: "proto_name", 26 | } 27 | 28 | Context("when call name(swapped) method", func() { 29 | 30 | It("returns field.Name if swapped==true", func() { 31 | name := field.name(true) 32 | Expect(name).To(Equal("name")) 33 | }) 34 | 35 | It("returns field.Name if swapped==false", func() { 36 | name := field.name(false) 37 | Expect(name).To(Equal("proto_name")) 38 | }) 39 | }) 40 | 41 | Context("when call convertFunc(swapped) method", func() { 42 | 43 | DescribeTable("check result", 44 | // func(swapped bool, expected string, f *Field) { 45 | func(g2p, p2g string, gg, pp, swapped bool, expected string) { 46 | f := &Field{ 47 | GoToProtoType: g2p, 48 | ProtoToGoType: p2g, 49 | GoIsPointer: gg, 50 | ProtoIsPointer: pp, 51 | } 52 | 53 | r := f.convertFunc(swapped) 54 | Expect(r).To(Equal(expected)) 55 | }, 56 | 57 | xEntry("go2proto", "proto2go", false, false, false, "proto2go"), 58 | xEntry("go2proto", "proto2go", false, false, true, "go2proto"), 59 | xEntry("go2proto", "proto2go", true, true, false, "proto2goPtr"), 60 | xEntry("go2proto", "proto2go", true, true, true, "go2protoPtr"), 61 | xEntry("go2proto", "proto2go", false, true, false, "proto2goPtrVal"), 62 | xEntry("go2proto", "proto2go", false, true, true, "go2protoValPtr"), 63 | xEntry("go2proto", "proto2go", true, false, false, "proto2goValPtr"), 64 | xEntry("go2proto", "proto2go", true, false, true, "go2protoPtrVal"), 65 | xEntry("go2protoList", "proto2goList", false, false, false, "proto2go"), 66 | xEntry("go2protoList", "proto2goList", false, false, true, "go2proto"), 67 | xEntry("go2protoList", "proto2goList", true, true, false, "proto2goListPtr"), 68 | xEntry("go2protoList", "proto2goList", true, true, true, "go2protoListPtr"), 69 | xEntry("go2protoList", "proto2goList", true, false, false, "proto2goValPtrList"), 70 | xEntry("go2protoList", "proto2goList", true, false, true, "go2protoPtrValList"), 71 | xEntry("go2protoList", "proto2goList", false, true, false, "proto2goPtrValList"), 72 | xEntry("go2protoList", "proto2goList", false, true, true, "go2protoValPtrList"), 73 | ) 74 | }) 75 | }) 76 | 77 | Describe("formatOneofField", func() { 78 | 79 | DescribeTable("check returns", 80 | func(f Field, swapped bool, pref, expected string) { 81 | r := formatOneofField(f, swapped, pref) 82 | Expect(r).To(Equal(expected)) 83 | }, 84 | 85 | Entry("Field has no oneof declaration", Field{ 86 | Name: "field_name", 87 | OneofDecl: "", 88 | }, false, "", `/* field "field_name" is not Oneof field*/`), 89 | 90 | Entry("ProtoToGoType is empty", Field{ 91 | ProtoName: "proto_name", 92 | OneofDecl: "oneof_decl_name", 93 | }, false, "", "src.proto_name"), 94 | 95 | Entry("ProtoToGoType is empty, swapped", Field{ 96 | ProtoType: "proto_type", 97 | OneofDecl: "oneof_decl_name", 98 | }, true, "prefix", "&prefix.proto_type{}"), 99 | 100 | Entry("ProtoToGoType is not empty", Field{ 101 | ProtoName: "proto_name", 102 | ProtoToGoType: "p2g", 103 | OneofDecl: "oneof_decl_name", 104 | }, false, "prefix", "p2g(src.proto_name)"), 105 | ) 106 | }) 107 | 108 | Describe("formatOneofInitField", func() { 109 | 110 | DescribeTable("check returns", 111 | func(f Field, swapped bool, expected string) { 112 | r := formatOneofInitField(f, swapped) 113 | Expect(r).To(Equal(expected)) 114 | }, 115 | Entry("Not oneof", Field{}, false, ""), 116 | Entry("Oneof field", Field{OneofDecl: "decl"}, false, ""), 117 | Entry("Not oneof, swapped", Field{}, true, ""), 118 | Entry("", Field{ 119 | OneofDecl: "oneof_decl_name", 120 | GoToProtoType: "g2p", 121 | Name: "field_name", 122 | ProtoName: "proto_name", 123 | }, true, " g2p(src.field_name, s.proto_name, version)"), 124 | ) 125 | }) 126 | 127 | Describe("formatComplexField", func() { 128 | 129 | DescribeTable("check returns", 130 | func(f Field, swapped bool, expected string) { 131 | r := formatComplexField(f, swapped) 132 | Expect(r).To(Equal(expected)) 133 | }, 134 | Entry("ProtoToGoType is not empty", Field{ 135 | Name: "name", 136 | ProtoName: "proto_name", 137 | GoToProtoType: "g2p", 138 | ProtoToGoType: "p2g", 139 | GoIsPointer: true, 140 | ProtoIsPointer: false, 141 | Opts: ", opts...", 142 | }, false, " p2gValPtr(src.proto_name , opts...)"), 143 | 144 | Entry("ProtoToGoType is empty", Field{ 145 | Name: "name", 146 | ProtoName: "proto_name", 147 | GoToProtoType: "g2p", 148 | ProtoToGoType: "", 149 | GoIsPointer: true, 150 | ProtoIsPointer: false, 151 | Opts: ", opts...", 152 | }, false, "src.proto_name"), 153 | ) 154 | }) 155 | 156 | Describe("formatField", func() { 157 | 158 | DescribeTable("check returns", 159 | func(f Field, swapped bool, pref, expected string) { 160 | r := formatField(f, swapped, pref) 161 | Expect(r).To(Equal(expected)) 162 | }, 163 | 164 | Entry("Not oneof", Field{ 165 | Name: "name", 166 | ProtoName: "proto_name", 167 | }, false, "", "name: src.proto_name,"), 168 | 169 | Entry("Not oneof, swapped", Field{ 170 | Name: "name", 171 | ProtoName: "proto_name", 172 | }, true, "", "proto_name: src.name,"), 173 | 174 | Entry("Oneof", Field{ 175 | Name: "name", 176 | ProtoName: "proto_name", 177 | OneofDecl: "oneof_decl_name", 178 | }, false, "prefix", "name: src.proto_name,"), 179 | 180 | Entry("Oneof, swapped", Field{ 181 | Name: "name", 182 | ProtoName: "proto_name", 183 | ProtoType: "proto_type", 184 | OneofDecl: "oneof_decl_name", 185 | }, true, "prefix", "proto_name: &prefix.proto_type{},"), 186 | ) 187 | }) 188 | 189 | Describe("Data.Swap", func() { 190 | 191 | Context("when Swap() called", func() { 192 | 193 | var d *Data 194 | 195 | BeforeEach(func() { 196 | d = &Data{ 197 | Src: "src", 198 | Dst: "dst", 199 | SrcPref: "src_pref", 200 | DstPref: "dst_pref", 201 | SrcFn: "src_fn", 202 | DstFn: "dst_fn", 203 | SrcPointer: "src_pointer", 204 | DstPointer: "dst_pointer", 205 | Swapped: false, 206 | } 207 | }) 208 | 209 | It("swap some fields", func() { 210 | d.swap() 211 | Expect(*d).To(MatchFields(IgnoreExtras, Fields{ 212 | "Src": Equal("dst"), 213 | "Dst": Equal("src"), 214 | "SrcPref": Equal("dst_pref"), 215 | "DstPref": Equal("src_pref"), 216 | "SrcFn": Equal("dst_fn"), 217 | "DstFn": Equal("src_fn"), 218 | "SrcPointer": Equal("dst_pointer"), 219 | "DstPointer": Equal("src_pointer"), 220 | "Swapped": BeTrue(), 221 | })) 222 | }) 223 | }) 224 | }) 225 | 226 | Describe("templateWithHelpers", func() { 227 | var w *bytes.Buffer 228 | 229 | BeforeEach(func() { 230 | w = bytes.NewBuffer([]byte{}) 231 | }) 232 | 233 | Context("when execute whole template", func() { 234 | 235 | It("returns full function set as string", func() { 236 | t, err := templateWithHelpers("test_template") 237 | Expect(err).NotTo(HaveOccurred()) 238 | 239 | err = t.Execute(w, Data{ 240 | SrcPref: "src_pref", 241 | Src: "src", 242 | SrcFn: "src_fn", 243 | SrcPointer: "src_pointer", 244 | DstPref: "dst_pref", 245 | Dst: "dst", 246 | DstFn: "dst_fn", 247 | DstPointer: "dst_pointer", 248 | Swapped: false, 249 | HelperPackage: "hp", 250 | Ptr: false, 251 | Fields: []Field{ 252 | { 253 | Name: "FirstField", 254 | ProtoName: "proto_name", 255 | ProtoType: "proto_type", 256 | ProtoToGoType: "FirstProto2go", 257 | GoToProtoType: "FirstGo2proto", 258 | GoIsPointer: false, 259 | ProtoIsPointer: false, 260 | UsePackage: false, 261 | OneofDecl: "", 262 | Opts: "", 263 | }, 264 | { 265 | Name: "SecondField", 266 | ProtoName: "proto_name2", 267 | ProtoType: "proto_type2", 268 | ProtoToGoType: "SecondProto2go", 269 | GoToProtoType: "SecondGo2proto", 270 | GoIsPointer: false, 271 | ProtoIsPointer: false, 272 | UsePackage: false, 273 | OneofDecl: "oneof_decl_name", 274 | Opts: "", 275 | }, 276 | }, 277 | }) 278 | Expect(err).NotTo(HaveOccurred()) 279 | }) 280 | 281 | }) 282 | 283 | }) 284 | 285 | Describe("Template parts", func() { 286 | var w *bytes.Buffer 287 | 288 | BeforeEach(func() { 289 | w = bytes.NewBuffer([]byte{}) 290 | }) 291 | 292 | Context("when execute template funcNameT", func() { 293 | 294 | It("return formatted string", func() { 295 | d := Data{SrcFn: "SrcFn", DstFn: "DstFn"} 296 | err := funcNameT.Execute(w, d) 297 | Expect(err).NotTo(HaveOccurred()) 298 | Expect(w.String()).To(Equal("SrcFnToDstFn")) 299 | }) 300 | 301 | }) 302 | 303 | Context("when execute template srcParamT", func() { 304 | 305 | DescribeTable("check result", 306 | func(d Data, expected string) { 307 | err := srcParamT.Execute(w, d) 308 | Expect(err).NotTo(HaveOccurred()) 309 | Expect(w.String()).To(Equal(expected)) 310 | }, 311 | Entry("Without prefix", Data{Src: "Src"}, "Src, opts ...TransformParam"), 312 | Entry("Withprefix", Data{SrcPref: "pref", Src: "Src"}, "pref.Src, opts ...TransformParam"), 313 | ) 314 | 315 | }) 316 | 317 | Context("when execute template dstParamT", func() { 318 | 319 | DescribeTable("check result", 320 | func(d Data, expected string) { 321 | err := dstParamT.Execute(w, d) 322 | Expect(err).NotTo(HaveOccurred()) 323 | Expect(w.String()).To(Equal(expected)) 324 | }, 325 | Entry("Without prefix", Data{Dst: "Dst"}, "Dst"), 326 | Entry("With prefix", Data{DstPref: "pref", Dst: "Dst"}, "pref.Dst"), 327 | ) 328 | 329 | }) 330 | 331 | Context("when execute template ptrValT", func() { 332 | 333 | DescribeTable("check result", 334 | func(d Data, expected string) { 335 | err := ptrValT.Execute(w, d) 336 | Expect(err).NotTo(HaveOccurred()) 337 | Expect(w.String()).To(Equal(expected)) 338 | }, 339 | Entry("Not swapped", Data{Swapped: false}, "PtrVal"), 340 | Entry("Swapped", Data{Swapped: true}, "ValPtr"), 341 | ) 342 | }) 343 | 344 | Context("when execute template ptrT", func() { 345 | 346 | DescribeTable("check result", 347 | func(d Data, expected string) { 348 | err := ptrT.Execute(w, d) 349 | Expect(err).NotTo(HaveOccurred()) 350 | Expect(w.String()).To(Equal(expected)) 351 | }, 352 | Entry("Ptr", Data{Ptr: true}, "Ptr"), 353 | Entry("Not Ptr", Data{Ptr: false}, "Val"), 354 | ) 355 | }) 356 | 357 | Context("when execute template ptrOnlyT", func() { 358 | 359 | DescribeTable("check result", 360 | func(d Data, expected string) { 361 | err := ptrOnlyT.Execute(w, d) 362 | Expect(err).NotTo(HaveOccurred()) 363 | Expect(w.String()).To(Equal(expected)) 364 | }, 365 | Entry("Ptr", Data{Ptr: true}, "Ptr"), 366 | Entry("Not Ptr", Data{Ptr: false}, ""), 367 | ) 368 | }) 369 | 370 | Context("when execute template starT", func() { 371 | 372 | DescribeTable("check result", 373 | func(d Data, expected string) { 374 | err := starT.Execute(w, d) 375 | Expect(err).NotTo(HaveOccurred()) 376 | Expect(w.String()).To(Equal(expected)) 377 | }, 378 | Entry("Ptr", Data{Ptr: true}, "*"), 379 | Entry("Not Ptr", Data{Ptr: false}, ""), 380 | ) 381 | }) 382 | 383 | Context("when execute template ptr2ptrT", func() { 384 | 385 | DescribeTable("check result", 386 | func(d Data, expected string) { 387 | err := ptr2ptrT.Execute(w, d) 388 | Expect(err).NotTo(HaveOccurred()) 389 | Expect(w.String()).To(Equal(expected)) 390 | }, 391 | Entry("Ptr", Data{ 392 | Src: "Src", 393 | SrcFn: "SrcFn", 394 | SrcPref: "SrcPref", 395 | Dst: "Dst", 396 | DstFn: "DstFn", 397 | DstPref: "DstPref", 398 | }, `func SrcFnToDstFnPtr(src *SrcPref.Src, opts ...TransformParam) *DstPref.Dst { 399 | if src == nil { 400 | return nil 401 | } 402 | 403 | d := SrcFnToDstFn(*src, opts...) 404 | return &d 405 | }`), 406 | ) 407 | }) 408 | 409 | Context("when execute template ptr2valT", func() { 410 | 411 | DescribeTable("check result", 412 | func(d Data, expected string) { 413 | err := ptr2valT.Execute(w, d) 414 | Expect(err).NotTo(HaveOccurred()) 415 | Expect(w.String()).To(Equal(expected)) 416 | }, 417 | Entry("Ptr", Data{ 418 | Src: "Src", 419 | SrcFn: "SrcFn", 420 | SrcPref: "SrcPref", 421 | Dst: "Dst", 422 | DstFn: "DstFn", 423 | DstPref: "DstPref", 424 | }, `func SrcFnToDstFnPtrVal(src *SrcPref.Src, opts ...TransformParam) DstPref.Dst { 425 | if src == nil { 426 | return DstPref.Dst{} 427 | } 428 | 429 | return SrcFnToDstFn(*src, opts...) 430 | }`), 431 | ) 432 | }) 433 | 434 | Context("when execute template val2ptrT", func() { 435 | 436 | DescribeTable("check result", 437 | func(d Data, expected string) { 438 | err := val2ptrT.Execute(w, d) 439 | Expect(err).NotTo(HaveOccurred()) 440 | Expect(w.String()).To(Equal(expected)) 441 | }, 442 | Entry("Ptr", Data{ 443 | Src: "Src", 444 | SrcFn: "SrcFn", 445 | SrcPref: "SrcPref", 446 | Dst: "Dst", 447 | DstFn: "DstFn", 448 | DstPref: "DstPref", 449 | }, `func SrcFnToDstFnValPtr(src SrcPref.Src, opts ...TransformParam) *DstPref.Dst { 450 | d := SrcFnToDstFn(src, opts...) 451 | return &d 452 | }`), 453 | ) 454 | }) 455 | 456 | Context("when execute template val2valT", func() { 457 | 458 | DescribeTable("check result", 459 | func(d Data, expected string) { 460 | err := val2valT.Execute(w, d) 461 | Expect(err).NotTo(HaveOccurred()) 462 | Expect(w.String()).To(Equal(expected)) 463 | }, 464 | Entry("Ptr", Data{ 465 | Src: "Src", 466 | SrcFn: "SrcFn", 467 | SrcPref: "SrcPref", 468 | Dst: "Dst", 469 | DstFn: "DstFn", 470 | DstPref: "DstPref", 471 | Fields: []Field{ 472 | { 473 | Name: "FirstField", 474 | ProtoName: "proto_name", 475 | ProtoType: "proto_type", 476 | ProtoToGoType: "FirstGo2proto", 477 | GoToProtoType: "FirstProto2go", 478 | GoIsPointer: false, 479 | ProtoIsPointer: false, 480 | UsePackage: false, 481 | OneofDecl: "", 482 | Opts: "", 483 | }, 484 | { 485 | Name: "SecondField", 486 | ProtoName: "proto_name2", 487 | ProtoType: "proto_type2", 488 | ProtoToGoType: "SecondProto2go", 489 | GoToProtoType: "SecondGo2proto", 490 | GoIsPointer: false, 491 | ProtoIsPointer: false, 492 | UsePackage: false, 493 | OneofDecl: "oneof_decl_name", 494 | Opts: "", 495 | }, 496 | }, 497 | }, `func SrcFnToDstFn(src SrcPref.Src, opts ...TransformParam) DstPref.Dst { 498 | s := DstPref.Dst{ 499 | FirstField: FirstGo2proto(src.proto_name ), 500 | SecondField: SecondProto2go(src.proto_name2), 501 | } 502 | 503 | applyOptions(opts...) 504 | 505 | 506 | 507 | return s 508 | }`), 509 | ) 510 | }) 511 | 512 | Context("when execute template lst2lstT", func() { 513 | 514 | DescribeTable("check result", 515 | func(d Data, expected string) { 516 | err := lst2lstT.Execute(w, d) 517 | Expect(err).NotTo(HaveOccurred()) 518 | Expect(w.String()).To(Equal(expected)) 519 | }, 520 | Entry("Ptr", Data{ 521 | Src: "Src", 522 | SrcFn: "SrcFn", 523 | SrcPref: "SrcPref", 524 | Dst: "Dst", 525 | DstFn: "DstFn", 526 | DstPref: "DstPref", 527 | }, `func SrcFnToDstFnValList(src []SrcPref.Src, opts ...TransformParam) []DstPref.Dst { 528 | resp := make([]DstPref.Dst, len(src)) 529 | 530 | for i, s := range src { 531 | resp[i] = SrcFnToDstFn(s, opts...) 532 | } 533 | 534 | return resp 535 | }`), 536 | ) 537 | }) 538 | 539 | Context("when execute template ptrlst2ptrlstT", func() { 540 | 541 | DescribeTable("check result", 542 | func(d Data, expected string) { 543 | err := ptrlst2ptrlstT.Execute(w, d) 544 | Expect(err).NotTo(HaveOccurred()) 545 | Expect(w.String()).To(Equal(expected)) 546 | }, 547 | Entry("Ptr", Data{ 548 | Src: "Src", 549 | SrcFn: "SrcFn", 550 | SrcPref: "SrcPref", 551 | Dst: "Dst", 552 | DstFn: "DstFn", 553 | DstPref: "DstPref", 554 | }, `func SrcFnToDstFnPtrList(src []*SrcPref.Src, opts ...TransformParam) []*DstPref.Dst { 555 | resp := make([]*DstPref.Dst, len(src)) 556 | 557 | for i, s := range src { 558 | resp[i] = SrcFnToDstFnPtr(s, opts...) 559 | } 560 | 561 | return resp 562 | }`), 563 | ) 564 | }) 565 | 566 | Context("when execute template vallst2vallstT", func() { 567 | 568 | DescribeTable("check result", 569 | func(d Data, expected string) { 570 | err := vallst2vallstT.Execute(w, d) 571 | Expect(err).NotTo(HaveOccurred()) 572 | Expect(w.String()).To(Equal(expected)) 573 | }, 574 | Entry("Ptr", Data{ 575 | Src: "Src", 576 | SrcFn: "SrcFn", 577 | SrcPref: "SrcPref", 578 | Dst: "Dst", 579 | DstFn: "DstFn", 580 | DstPref: "DstPref", 581 | }, `func SrcFnToDstFnValList(src []SrcPref.Src, opts ...TransformParam) []DstPref.Dst { 582 | resp := make([]DstPref.Dst, len(src)) 583 | 584 | for i, s := range src { 585 | resp[i] = SrcFnToDstFn(s, opts...) 586 | } 587 | 588 | return resp 589 | }`), 590 | ) 591 | }) 592 | 593 | Context("when execute template ptrlst2vallstT", func() { 594 | 595 | DescribeTable("check result", 596 | func(d Data, expected string) { 597 | err := ptrlst2vallstT.Execute(w, d) 598 | Expect(err).NotTo(HaveOccurred()) 599 | Expect(w.String()).To(Equal(expected)) 600 | }, 601 | Entry("Ptr", Data{ 602 | Src: "Src", 603 | SrcFn: "SrcFn", 604 | SrcPref: "SrcPref", 605 | Dst: "Dst", 606 | DstFn: "DstFn", 607 | DstPref: "DstPref", 608 | }, `func SrcFnToDstFnPtrValList(src []SrcPref.Src, opts ...TransformParam) []DstPref.Dst { 609 | resp := make([]DstPref.Dst, len(src)) 610 | 611 | for i, s := range src { 612 | resp[i] = SrcFnToDstFn(*s) 613 | } 614 | 615 | return resp 616 | }`), 617 | ) 618 | }) 619 | 620 | Context("when execute template ptr2vallstT", func() { 621 | 622 | DescribeTable("check result", 623 | func(d Data, expected string) { 624 | err := ptr2vallstT.Execute(w, d) 625 | Expect(err).NotTo(HaveOccurred()) 626 | Expect(w.String()).To(Equal(expected)) 627 | }, 628 | Entry("Ptr", Data{ 629 | Src: "Src", 630 | SrcFn: "SrcFn", 631 | SrcPref: "SrcPref", 632 | Dst: "Dst", 633 | DstFn: "DstFn", 634 | DstPref: "DstPref", 635 | }, `// SrcFnToDstFnList is DEPRECATED. Use SrcFnToDstFnPtrValList instead. 636 | func SrcFnToDstFnList(src []SrcPref.Src, opts ...TransformParam) []DstPref.Dst { 637 | return SrcFnToDstFnPtrValList(src) 638 | }`), 639 | ) 640 | }) 641 | }) 642 | 643 | }) 644 | -------------------------------------------------------------------------------- /generator/testdata/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Product struct { 4 | ID int `db:"id" json:"id"` 5 | } 6 | -------------------------------------------------------------------------------- /generator/testdata/processfile.go.golden: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-struct-transformer, version: v0.0.1. DO NOT EDIT. 2 | // source file: product.proto 3 | // source package: pb 4 | 5 | package product 6 | func PbToProductPtr(src *pb1.Product, opts ...TransformParam) *repo1.Product { 7 | if src == nil { 8 | return nil 9 | } 10 | 11 | d := PbToProduct(*src, opts...) 12 | return &d 13 | } 14 | 15 | func PbToProductPtrList(src []*pb1.Product, opts ...TransformParam) []*repo1.Product { 16 | resp := make([]*repo1.Product, len(src)) 17 | 18 | for i, s := range src { 19 | resp[i] = PbToProductPtr(s, opts...) 20 | } 21 | 22 | return resp 23 | } 24 | 25 | func PbToProductPtrVal(src *pb1.Product, opts ...TransformParam) repo1.Product { 26 | if src == nil { 27 | return repo1.Product{} 28 | } 29 | 30 | return PbToProduct(*src, opts...) 31 | } 32 | 33 | func PbToProductPtrValList(src []*pb1.Product, opts ...TransformParam) []repo1.Product { 34 | resp := make([]repo1.Product, len(src)) 35 | 36 | for i, s := range src { 37 | resp[i] = PbToProduct(*s) 38 | } 39 | 40 | return resp 41 | } 42 | 43 | // PbToProductList is DEPRECATED. Use PbToProductPtrValList instead. 44 | func PbToProductList(src []*pb1.Product, opts ...TransformParam) []repo1.Product { 45 | return PbToProductPtrValList(src) 46 | } 47 | 48 | func PbToProduct(src pb1.Product, opts ...TransformParam) repo1.Product { 49 | s := repo1.Product{ 50 | ID: int(src.Id ), 51 | } 52 | 53 | applyOptions(opts...) 54 | 55 | 56 | return s 57 | } 58 | 59 | func PbToProductValPtr(src pb1.Product, opts ...TransformParam) *repo1.Product { 60 | d := PbToProduct(src, opts...) 61 | return &d 62 | } 63 | 64 | func PbToProductValList(src []pb1.Product, opts ...TransformParam) []repo1.Product { 65 | resp := make([]repo1.Product, len(src)) 66 | 67 | for i, s := range src { 68 | resp[i] = PbToProduct(s, opts...) 69 | } 70 | 71 | return resp 72 | } 73 | 74 | func ProductToPbPtr(src *repo1.Product, opts ...TransformParam) *pb1.Product { 75 | if src == nil { 76 | return nil 77 | } 78 | 79 | d := ProductToPb(*src, opts...) 80 | return &d 81 | } 82 | 83 | func ProductToPbPtrList(src []*repo1.Product, opts ...TransformParam) []*pb1.Product { 84 | resp := make([]*pb1.Product, len(src)) 85 | 86 | for i, s := range src { 87 | resp[i] = ProductToPbPtr(s, opts...) 88 | } 89 | 90 | return resp 91 | } 92 | 93 | func ProductToPbPtrVal(src *repo1.Product, opts ...TransformParam) pb1.Product { 94 | if src == nil { 95 | return pb1.Product{} 96 | } 97 | 98 | return ProductToPb(*src, opts...) 99 | } 100 | 101 | func ProductToPbValPtrList(src []repo1.Product, opts ...TransformParam) []*pb1.Product { 102 | resp := make([]*pb1.Product, len(src)) 103 | 104 | for i, s := range src { 105 | g := ProductToPb(s, opts...) 106 | resp[i] = &g 107 | } 108 | 109 | return resp 110 | } 111 | 112 | // ProductToPbList is DEPRECATED. Use ProductToPbValPtrList instead. 113 | func ProductToPbList(src []repo1.Product, opts ...TransformParam) []*pb1.Product { 114 | return ProductToPbValPtrList(src) 115 | } 116 | 117 | func ProductToPb(src repo1.Product, opts ...TransformParam) pb1.Product { 118 | s := pb1.Product{ 119 | Id: int64(src.ID ), 120 | } 121 | 122 | applyOptions(opts...) 123 | 124 | 125 | return s 126 | } 127 | 128 | func ProductToPbValPtr(src repo1.Product, opts ...TransformParam) *pb1.Product { 129 | d := ProductToPb(src, opts...) 130 | return &d 131 | } 132 | 133 | func ProductToPbValList(src []repo1.Product, opts ...TransformParam) []pb1.Product { 134 | resp := make([]pb1.Product, len(src)) 135 | 136 | for i, s := range src { 137 | resp[i] = ProductToPb(s, opts...) 138 | } 139 | 140 | return resp 141 | } 142 | 143 | -------------------------------------------------------------------------------- /generator/types.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 4 | 5 | type typeRel struct { 6 | pbType string 7 | goType string 8 | usePackage bool 9 | } 10 | 11 | // types contains protobuf types. 12 | // default mapping for similar but non-equal types. 13 | var types = map[descriptor.FieldDescriptorProto_Type]typeRel{ 14 | descriptor.FieldDescriptorProto_TYPE_INT32: typeRel{pbType: "int32", goType: "int"}, 15 | descriptor.FieldDescriptorProto_TYPE_INT64: typeRel{pbType: "int64", goType: "int"}, 16 | descriptor.FieldDescriptorProto_TYPE_UINT32: typeRel{pbType: "uint32", goType: "uint"}, 17 | descriptor.FieldDescriptorProto_TYPE_UINT64: typeRel{pbType: "uint64", goType: "uint"}, 18 | descriptor.FieldDescriptorProto_TYPE_FLOAT: typeRel{pbType: "", goType: "float32"}, 19 | descriptor.FieldDescriptorProto_TYPE_DOUBLE: typeRel{pbType: "", goType: "float64"}, 20 | descriptor.FieldDescriptorProto_TYPE_BOOL: typeRel{pbType: "", goType: "bool"}, 21 | descriptor.FieldDescriptorProto_TYPE_STRING: typeRel{pbType: "", goType: "string"}, 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bold-commerce/protoc-gen-struct-transformer 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/gogo/protobuf v1.3.1 7 | github.com/golang/protobuf v1.5.0 8 | github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 9 | github.com/onsi/ginkgo/v2 v2.22.2 10 | github.com/onsi/gomega v1.36.2 11 | github.com/pkg/errors v0.8.1 12 | golang.org/x/tools v0.28.0 13 | ) 14 | 15 | require ( 16 | github.com/go-logr/logr v1.4.2 // indirect 17 | github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 18 | github.com/google/go-cmp v0.6.0 // indirect 19 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect 20 | golang.org/x/mod v0.22.0 // indirect 21 | golang.org/x/net v0.33.0 // indirect 22 | golang.org/x/sync v0.10.0 // indirect 23 | golang.org/x/sys v0.28.0 // indirect 24 | golang.org/x/text v0.21.0 // indirect 25 | google.golang.org/protobuf v1.36.1 // indirect 26 | gopkg.in/yaml.v3 v3.0.1 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 4 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 5 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 6 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 7 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 8 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 9 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 10 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 11 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 12 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 13 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 14 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 15 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 16 | github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8= 17 | github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= 18 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 19 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 20 | github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= 21 | github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 22 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 23 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 24 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 25 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 26 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 28 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 29 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 30 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 31 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 32 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 33 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 34 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 35 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 36 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 37 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 38 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 39 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 40 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 41 | golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= 42 | golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= 43 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 44 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 45 | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 46 | google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 50 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Transformation function generator for gRPC. 2 | // 3 | // Overview 4 | // 5 | // Protocol buffers complier (protoc) https://github.com/protocolbuffers/protobuf 6 | // generates structures based on message definition in *.proto file. It's 7 | // possible to use these generated structures directly, but it's better to have 8 | // clear separation between transport level (gRPC) and business logic with its 9 | // own structures. In this case you have to convert generated structures into 10 | // structures use in business logic and vice versa. 11 | // 12 | // See documentation and usage examples on https://github.com/bold-commerce/protoc-gen-struct-transformer/blob/master/README.md 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "io/ioutil" 19 | "log" 20 | "os" 21 | "path/filepath" 22 | 23 | "github.com/bold-commerce/protoc-gen-struct-transformer/generator" 24 | plugin "github.com/gogo/protobuf/protoc-gen-gogo/plugin" 25 | "github.com/golang/protobuf/proto" 26 | "golang.org/x/tools/imports" 27 | ) 28 | 29 | var ( 30 | packageName = flag.String("package", "fallback", "Package name for generated functions.") 31 | helperPackageName = flag.String("helper-package", "", "Package name for helper functions.") 32 | versionFlag = flag.Bool("version", false, "Print current version.") 33 | goimports = flag.Bool("goimports", false, "Perform goimports on generated file.") 34 | debug = flag.Bool("debug", false, "Add debug information to generated file.") 35 | usePackageInPath = flag.Bool("use-package-in-path", true, "If true, package parameter will be used in path for output file.") 36 | ) 37 | 38 | func main() { 39 | flag.Parse() 40 | if *versionFlag { 41 | fmt.Println(generator.Version()) 42 | os.Exit(0) 43 | } 44 | 45 | var gogoreq plugin.CodeGeneratorRequest 46 | 47 | data, err := ioutil.ReadAll(os.Stdin) 48 | must(err) 49 | must(proto.Unmarshal(data, &gogoreq)) 50 | 51 | // Convert incoming parameters into CLI flags. 52 | must(generator.SetParameters(flag.CommandLine, gogoreq.Parameter)) 53 | 54 | resp := &plugin.CodeGeneratorResponse{} 55 | optPath := "" 56 | 57 | messages, err := generator.CollectAllMessages(gogoreq) 58 | must(err) 59 | 60 | for _, f := range gogoreq.ProtoFile { 61 | 62 | filename, content, err := generator.ProcessFile(f, packageName, helperPackageName, messages, *debug, *usePackageInPath) 63 | if err != nil { 64 | if err != generator.ErrFileSkipped { 65 | must(err) 66 | } 67 | continue 68 | } 69 | 70 | content, err = runGoimports(filename, content) 71 | if err != nil { 72 | if err != generator.ErrFileSkipped { 73 | must(err) 74 | } 75 | continue 76 | } 77 | 78 | resp.File = append(resp.File, &plugin.CodeGeneratorResponse_File{ 79 | Name: proto.String(filename), 80 | Content: proto.String(content), 81 | }) 82 | 83 | optPath = filename 84 | } 85 | 86 | if optPath != "" { 87 | optPath = filepath.Dir(optPath) + "/options.go" 88 | 89 | content, err := runGoimports(optPath, generator.OptHelpers(*packageName)) 90 | if err != nil { 91 | if err != generator.ErrFileSkipped { 92 | must(err) 93 | } 94 | } 95 | 96 | resp.File = append(resp.File, &plugin.CodeGeneratorResponse_File{ 97 | Name: proto.String(optPath), 98 | Content: proto.String(content), 99 | }) 100 | } 101 | 102 | // Send back the results. 103 | data, err = proto.Marshal(resp) 104 | must(err) 105 | 106 | _, err = os.Stdout.Write(data) 107 | must(err) 108 | } 109 | 110 | func must(err error) { 111 | if err != nil { 112 | if *debug { 113 | log.Fatalf("%+v", err) 114 | } else { 115 | log.Fatalf("%v", err) 116 | } 117 | } 118 | } 119 | 120 | func runGoimports(filename, content string) (string, error) { 121 | if !*goimports { 122 | return content, nil 123 | } 124 | 125 | formatted, err := imports.Process(filename, []byte(content), nil) 126 | return string(formatted), err 127 | } 128 | -------------------------------------------------------------------------------- /options/annotations.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: options/annotations.proto 3 | 4 | // Package transformer contains extend options for protobuf files, messages and 5 | // fields. 6 | // Options are used for customizing transformation process. 7 | 8 | package options 9 | 10 | import ( 11 | fmt "fmt" 12 | proto "github.com/gogo/protobuf/proto" 13 | descriptor "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 14 | math "math" 15 | ) 16 | 17 | // Reference imports to suppress errors if they are not otherwise used. 18 | var _ = proto.Marshal 19 | var _ = fmt.Errorf 20 | var _ = math.Inf 21 | 22 | // This is a compile-time assertion to ensure that this generated file 23 | // is compatible with the proto package it is being compiled against. 24 | // A compilation error at this line likely means your copy of the 25 | // proto package needs to be updated. 26 | const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package 27 | 28 | var E_GoModelsFilePath = &proto.ExtensionDesc{ 29 | ExtendedType: (*descriptor.FileOptions)(nil), 30 | ExtensionType: (*string)(nil), 31 | Field: 5201, 32 | Name: "transformer.go_models_file_path", 33 | Tag: "bytes,5201,opt,name=go_models_file_path", 34 | Filename: "options/annotations.proto", 35 | } 36 | 37 | var E_GoRepoPackage = &proto.ExtensionDesc{ 38 | ExtendedType: (*descriptor.FileOptions)(nil), 39 | ExtensionType: (*string)(nil), 40 | Field: 5202, 41 | Name: "transformer.go_repo_package", 42 | Tag: "bytes,5202,opt,name=go_repo_package", 43 | Filename: "options/annotations.proto", 44 | } 45 | 46 | var E_GoProtobufPackage = &proto.ExtensionDesc{ 47 | ExtendedType: (*descriptor.FileOptions)(nil), 48 | ExtensionType: (*string)(nil), 49 | Field: 5203, 50 | Name: "transformer.go_protobuf_package", 51 | Tag: "bytes,5203,opt,name=go_protobuf_package", 52 | Filename: "options/annotations.proto", 53 | } 54 | 55 | var E_GoStruct = &proto.ExtensionDesc{ 56 | ExtendedType: (*descriptor.MessageOptions)(nil), 57 | ExtensionType: (*string)(nil), 58 | Field: 5100, 59 | Name: "transformer.go_struct", 60 | Tag: "bytes,5100,opt,name=go_struct", 61 | Filename: "options/annotations.proto", 62 | } 63 | 64 | var E_Embed = &proto.ExtensionDesc{ 65 | ExtendedType: (*descriptor.FieldOptions)(nil), 66 | ExtensionType: (*bool)(nil), 67 | Field: 5300, 68 | Name: "transformer.embed", 69 | Tag: "varint,5300,opt,name=embed", 70 | Filename: "options/annotations.proto", 71 | } 72 | 73 | var E_Skip = &proto.ExtensionDesc{ 74 | ExtendedType: (*descriptor.FieldOptions)(nil), 75 | ExtensionType: (*bool)(nil), 76 | Field: 5301, 77 | Name: "transformer.skip", 78 | Tag: "varint,5301,opt,name=skip", 79 | Filename: "options/annotations.proto", 80 | } 81 | 82 | var E_MapTo = &proto.ExtensionDesc{ 83 | ExtendedType: (*descriptor.FieldOptions)(nil), 84 | ExtensionType: (*string)(nil), 85 | Field: 5303, 86 | Name: "transformer.map_to", 87 | Tag: "bytes,5303,opt,name=map_to", 88 | Filename: "options/annotations.proto", 89 | } 90 | 91 | var E_MapAs = &proto.ExtensionDesc{ 92 | ExtendedType: (*descriptor.FieldOptions)(nil), 93 | ExtensionType: (*string)(nil), 94 | Field: 5304, 95 | Name: "transformer.map_as", 96 | Tag: "bytes,5304,opt,name=map_as", 97 | Filename: "options/annotations.proto", 98 | } 99 | 100 | var E_Custom = &proto.ExtensionDesc{ 101 | ExtendedType: (*descriptor.FieldOptions)(nil), 102 | ExtensionType: (*bool)(nil), 103 | Field: 5305, 104 | Name: "transformer.custom", 105 | Tag: "varint,5305,opt,name=custom", 106 | Filename: "options/annotations.proto", 107 | } 108 | 109 | func init() { 110 | proto.RegisterExtension(E_GoModelsFilePath) 111 | proto.RegisterExtension(E_GoRepoPackage) 112 | proto.RegisterExtension(E_GoProtobufPackage) 113 | proto.RegisterExtension(E_GoStruct) 114 | proto.RegisterExtension(E_Embed) 115 | proto.RegisterExtension(E_Skip) 116 | proto.RegisterExtension(E_MapTo) 117 | proto.RegisterExtension(E_MapAs) 118 | proto.RegisterExtension(E_Custom) 119 | } 120 | 121 | func init() { proto.RegisterFile("options/annotations.proto", fileDescriptor_5df765dc541320cc) } 122 | 123 | var fileDescriptor_5df765dc541320cc = []byte{ 124 | // 353 bytes of a gzipped FileDescriptorProto 125 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0xd2, 0xbf, 0x4e, 0xfb, 0x30, 126 | 0x10, 0xc0, 0xf1, 0x46, 0xfa, 0xb5, 0xbf, 0xd6, 0x08, 0x01, 0x61, 0x01, 0x04, 0xa1, 0x4c, 0xb4, 127 | 0x4b, 0x2a, 0xf1, 0x6f, 0x88, 0xc4, 0x00, 0x12, 0x4c, 0x54, 0x54, 0x85, 0x89, 0xc5, 0x72, 0x93, 128 | 0xab, 0x1b, 0x35, 0xc9, 0x59, 0xb6, 0xfb, 0x1e, 0x3c, 0x0c, 0x08, 0x78, 0x03, 0xc6, 0x02, 0x0b, 129 | 0x23, 0x6a, 0x57, 0x1e, 0x02, 0x61, 0x37, 0x30, 0x80, 0x14, 0xb6, 0x48, 0x77, 0x9f, 0xaf, 0x6f, 130 | 0x08, 0x59, 0x45, 0xa1, 0x63, 0xcc, 0x54, 0x8b, 0x65, 0x19, 0x6a, 0x66, 0xbe, 0x7d, 0x21, 0x51, 131 | 0xa3, 0x3b, 0xa7, 0x25, 0xcb, 0x54, 0x1f, 0x65, 0x0a, 0x72, 0xad, 0xce, 0x11, 0x79, 0x02, 0x2d, 132 | 0x33, 0xea, 0x8d, 0xfa, 0xad, 0x08, 0x54, 0x28, 0x63, 0xa1, 0x51, 0xda, 0xf5, 0xe0, 0x8c, 0x2c, 133 | 0x73, 0xa4, 0x29, 0x46, 0x90, 0x28, 0xda, 0x8f, 0x13, 0xa0, 0x82, 0xe9, 0x81, 0xbb, 0xee, 0x5b, 134 | 0xe9, 0xe7, 0xd2, 0x3f, 0x8d, 0x13, 0x38, 0xb7, 0xaf, 0xae, 0x3c, 0x35, 0xea, 0x4e, 0xa3, 0xd6, 135 | 0x5d, 0xe4, 0xd8, 0x36, 0xf0, 0x73, 0xd6, 0x61, 0x7a, 0x10, 0x9c, 0x90, 0x05, 0x8e, 0x54, 0x82, 136 | 0x40, 0x2a, 0x58, 0x38, 0x64, 0x1c, 0x0a, 0x4a, 0xcf, 0xb6, 0x34, 0xcf, 0xb1, 0x0b, 0x02, 0x3b, 137 | 0xd6, 0x04, 0x6d, 0x73, 0x54, 0x0e, 0xfe, 0x98, 0x7a, 0xb1, 0xa9, 0x25, 0x8e, 0x9d, 0xd9, 0x38, 138 | 0xcf, 0x1d, 0x92, 0x1a, 0x47, 0xaa, 0xb4, 0x1c, 0x85, 0xda, 0xdd, 0xfc, 0x11, 0x69, 0x83, 0x52, 139 | 0x8c, 0x7f, 0x75, 0xde, 0xb7, 0x4d, 0xa7, 0xca, 0xf1, 0xc2, 0x88, 0x60, 0x8f, 0x94, 0x21, 0xed, 140 | 0x41, 0xe4, 0x6e, 0xfc, 0xf2, 0x3e, 0x24, 0x51, 0x0e, 0x6f, 0x9a, 0x75, 0xa7, 0x51, 0xed, 0xda, 141 | 0xe5, 0x60, 0x87, 0xfc, 0x53, 0xc3, 0x58, 0x14, 0xa1, 0x5b, 0x8b, 0xcc, 0x6e, 0xb0, 0x4f, 0x2a, 142 | 0x29, 0x13, 0x54, 0x63, 0x91, 0xba, 0x6b, 0x9a, 0x1b, 0xcb, 0x29, 0x13, 0x97, 0x98, 0x33, 0xa6, 143 | 0x8a, 0xd8, 0xfd, 0x37, 0x3b, 0x52, 0xc1, 0x01, 0xa9, 0x84, 0x23, 0xa5, 0x31, 0x2d, 0x62, 0x0f, 144 | 0xf6, 0xc6, 0xd9, 0xf6, 0xf1, 0xd6, 0xe3, 0xc4, 0x73, 0xc6, 0x13, 0xcf, 0x79, 0x9b, 0x78, 0xce, 145 | 0xf5, 0xd4, 0x2b, 0x8d, 0xa7, 0x5e, 0xe9, 0x75, 0xea, 0x95, 0xae, 0xfe, 0xcf, 0x7e, 0xcb, 0x5e, 146 | 0xc5, 0x84, 0x76, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x58, 0x4b, 0xc5, 0xea, 0xa8, 0x02, 0x00, 147 | 0x00, 148 | } 149 | -------------------------------------------------------------------------------- /options/annotations.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Package transformer contains extend options for protobuf files, messages and 4 | // fields. 5 | // Options are used for customizing transformation process. 6 | package transformer; 7 | 8 | option go_package = "github.com/bold-commerce/protoc-gen-struct-transformer/options"; 9 | 10 | import "google/protobuf/descriptor.proto"; 11 | 12 | extend google.protobuf.FileOptions { 13 | // Path to source file with Go structures which will be used as destination. 14 | string go_models_file_path = 5201; 15 | // Package name which contains model structures. 16 | string go_repo_package = 5202; 17 | // Package name with protobuf srtuctures. 18 | string go_protobuf_package = 5203; 19 | } 20 | 21 | extend google.protobuf.MessageOptions { 22 | // Name of structure from repo package. 23 | string go_struct = 5100; 24 | } 25 | 26 | extend google.protobuf.FieldOptions { 27 | // Embed is used when transformed structures should be embed into parent one. 28 | // It's the same as gogoproto.embed flag, but right now I can't read 29 | // gogoproto.embed option. 30 | // DEPRECATED, use gogooproto.embed instead. 31 | bool embed = 5300; 32 | // If true, field will not be used in transform functions. 33 | bool skip = 5301; 34 | // Points destination field type for OneOf fields. 35 | // string one_of_to = 5302; 36 | // Contains model's field name if it's different from name in messages. 37 | string map_to = 5303; 38 | // Contains name which will be used instead of current field name. 39 | // 40 | // string street_1 = 1; -> pb.go Street_1 instead Street1 41 | string map_as = 5304; 42 | // If true, the custom transformer will be used for the field. 43 | bool custom = 5305; 44 | } 45 | -------------------------------------------------------------------------------- /options/doc.go: -------------------------------------------------------------------------------- 1 | // Package options contains custom options which can be used in proto file. 2 | // 3 | // See annotations.proto file for detiled information about options. 4 | package options 5 | -------------------------------------------------------------------------------- /source/doc.go: -------------------------------------------------------------------------------- 1 | // Package source contains functions for parsing Go source files. 2 | // 3 | // This package is used for extract information about structures and their 4 | // fields from Go source files. 5 | package source 6 | -------------------------------------------------------------------------------- /source/field.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import "fmt" 4 | 5 | type ( 6 | // FieldInfo contains information about one structure field without field name. 7 | FieldInfo struct { 8 | // Field type name. 9 | Type string 10 | // Equals true if field is a pointer. 11 | IsPointer bool 12 | } 13 | 14 | // Structure is a set of fields of one structure. 15 | Structure map[string]FieldInfo 16 | // StructureList is a list of parsed structures. 17 | StructureList map[string]Structure 18 | ) 19 | 20 | // String return structure information as a string. 21 | func (s Structure) String() string { 22 | c := "\n// Target struct fields:\n" 23 | for k, v := range s { 24 | c += fmt.Sprintf("// Field: %q, Type: %q, isPointer: %t\n", k, v.Type, v.IsPointer) 25 | } 26 | c += "\n" 27 | return c 28 | } 29 | 30 | func (fi FieldInfo) String() string { 31 | if fi.IsPointer { 32 | return "*" + fi.Type 33 | } 34 | return fi.Type 35 | } 36 | -------------------------------------------------------------------------------- /source/parser.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "io" 9 | "reflect" 10 | "strconv" 11 | ) 12 | 13 | // inspect is a function which is run for each node in source file. See go/ast 14 | // package for details. 15 | func inspect(output StructureList) func(n ast.Node) bool { 16 | return func(n ast.Node) bool { 17 | spec, ok := n.(*ast.TypeSpec) 18 | if !ok { 19 | // skip non-types 20 | return true 21 | } 22 | 23 | if spec.Type == nil { 24 | // skip empty types 25 | return true 26 | } 27 | 28 | s, ok := spec.Type.(*ast.StructType) 29 | if !ok { 30 | // skip non-struct types 31 | return true 32 | } 33 | 34 | structName := spec.Name.Name 35 | if _, ok := output[structName]; !ok { 36 | output[structName] = Structure{} 37 | } 38 | 39 | embeddedCounter := 0 40 | for _, field := range s.Fields.List { 41 | fname := "embedded_" 42 | // Embedded strcuts have no names. 43 | if field.Names != nil { 44 | fname = field.Names[0].Name 45 | } else { 46 | fname += strconv.Itoa(embeddedCounter) 47 | embeddedCounter++ 48 | } 49 | 50 | switch t := field.Type.(type) { 51 | case *ast.Ident: // simple types e.g. int, string, etc. 52 | output[structName][fname] = FieldInfo{Type: t.Name} 53 | 54 | case *ast.SelectorExpr: // types like time.Time, time.Duration, nulls.String 55 | typ := fmt.Sprintf("%s.%s", t.X.(*ast.Ident).Name, t.Sel.Name) 56 | output[structName][fname] = FieldInfo{Type: typ} 57 | 58 | case *ast.StarExpr: // pointer to something 59 | switch se := t.X.(type) { 60 | case *ast.Ident: // *SomeStruct, *string, *int etc. 61 | typ := se.Name 62 | output[structName][fname] = FieldInfo{Type: typ, IsPointer: true} 63 | case *ast.SelectorExpr: // *time.Time 64 | typ := fmt.Sprintf("%s.%s", se.X.(*ast.Ident).Name, se.Sel.Name) 65 | output[structName][fname] = FieldInfo{Type: typ, IsPointer: true} 66 | default: 67 | typ := fmt.Sprintf("%s", reflect.TypeOf(t)) 68 | output[structName]["unsupported_star_expr_"+typ] = FieldInfo{Type: fmt.Sprintf("%T", se)} 69 | return true 70 | } 71 | 72 | case *ast.ArrayType: 73 | typ := "empty_type" 74 | switch at := t.Elt.(type) { 75 | case *ast.SelectorExpr: 76 | typ = at.Sel.Name 77 | case *ast.Ident: 78 | typ = at.Name 79 | default: 80 | typ := fmt.Sprintf("%s", reflect.TypeOf(t)) 81 | output[structName]["unsupported_array_type_"+typ] = FieldInfo{Type: fmt.Sprintf("%T", at)} 82 | return true 83 | } 84 | output[structName][fname] = FieldInfo{Type: typ} 85 | 86 | default: 87 | typ := fmt.Sprintf("%s", reflect.TypeOf(t)) 88 | output[structName]["unsupported_"+typ] = FieldInfo{Type: typ} 89 | } 90 | } 91 | return false 92 | } 93 | } 94 | 95 | // Parse gets path to source file or content of source file as a io.Reader and 96 | // run inspect functions on it. Function returns list of structures with their 97 | // fields. 98 | func Parse(path string, src io.Reader) (StructureList, error) { 99 | node, err := parser.ParseFile(token.NewFileSet(), path, src, 0) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | info := StructureList{} 105 | 106 | ast.Inspect(node, inspect(info)) 107 | 108 | return info, nil 109 | } 110 | 111 | // Lookup return structure by name from parsed source file or an error if 112 | // structure with such name not found. 113 | func Lookup(sl StructureList, structName string) (Structure, error) { 114 | f, ok := sl[structName] 115 | if !ok { 116 | return f, fmt.Errorf("structure %q not found", structName) 117 | } 118 | 119 | return f, nil 120 | } 121 | -------------------------------------------------------------------------------- /source/parser_test.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "bytes" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | var _ = Describe("Parser", func() { 10 | 11 | DescribeTable("check result", 12 | func(fileContent string, expected StructureList) { 13 | str, err := Parse("file.go", bytes.NewReader([]byte(fileContent))) 14 | Expect(err).NotTo(HaveOccurred()) 15 | 16 | Expect(str).To(Equal(expected)) 17 | }, 18 | 19 | Entry("File without structures", `package model`, StructureList{}), 20 | 21 | Entry("File with non-struct types", `package model 22 | 23 | type myInt int 24 | type myString string 25 | `, StructureList{}), 26 | 27 | Entry("File with one struct", `package model 28 | 29 | type ( 30 | MyStruct struct { 31 | I int 32 | PI *int 33 | S string 34 | PS *string 35 | } 36 | )`, StructureList{ 37 | "MyStruct": { 38 | "I": {Type: "int", IsPointer: false}, 39 | "PI": {Type: "int", IsPointer: true}, 40 | "S": {Type: "string", IsPointer: false}, 41 | "PS": {Type: "string", IsPointer: true}, 42 | }, 43 | }), 44 | 45 | Entry("File with two structs, one is embedded into another", `package model 46 | 47 | type ( 48 | Comment struct { 49 | Content string 50 | } 51 | 52 | MyStruct struct { 53 | ID int 54 | Name string 55 | Comment 56 | } 57 | )`, StructureList{ 58 | "Comment": { 59 | "Content": {Type: "string", IsPointer: false}, 60 | }, 61 | "MyStruct": { 62 | "embedded_0": {Type: "Comment", IsPointer: false}, 63 | "Name": {Type: "string", IsPointer: false}, 64 | "ID": {Type: "int", IsPointer: false}, 65 | }, 66 | }), 67 | 68 | Entry("File with two independent structs", `package model 69 | 70 | type ( 71 | Comment struct { 72 | ID int 73 | Content string 74 | } 75 | 76 | MyStruct struct { 77 | ID int 78 | Name string 79 | } 80 | )`, StructureList{ 81 | "Comment": { 82 | "ID": {Type: "int", IsPointer: false}, 83 | "Content": {Type: "string", IsPointer: false}, 84 | }, 85 | "MyStruct": { 86 | "Name": {Type: "string", IsPointer: false}, 87 | "ID": {Type: "int", IsPointer: false}, 88 | }, 89 | }), 90 | 91 | Entry("File with one struct, fields are of type SelectorExpr: time.Time, etc.", `package model 92 | 93 | type ( 94 | MyStruct struct { 95 | ID int 96 | Name string 97 | CreatedAt time.Time 98 | } 99 | )`, StructureList{ 100 | "MyStruct": { 101 | "ID": {Type: "int", IsPointer: false}, 102 | "Name": {Type: "string", IsPointer: false}, 103 | "CreatedAt": {Type: "time.Time", IsPointer: false}, 104 | }, 105 | }), 106 | 107 | Entry("File with one struct, fields are of slice type.", `package model 108 | 109 | type ( 110 | MyStruct struct { 111 | ID int 112 | Name string 113 | SubMyStructs []int 114 | } 115 | )`, StructureList{ 116 | "MyStruct": { 117 | "ID": {Type: "int", IsPointer: false}, 118 | "Name": {Type: "string", IsPointer: false}, 119 | "SubMyStructs": {Type: "int", IsPointer: false}, 120 | }, 121 | }), 122 | 123 | Entry("File with one struct, fields are of struct slice type.", `package model 124 | 125 | type ( 126 | MyStruct struct { 127 | ID int 128 | Name string 129 | Tags []nulls.String 130 | } 131 | )`, StructureList{ 132 | "MyStruct": { 133 | "ID": {Type: "int", IsPointer: false}, 134 | "Name": {Type: "string", IsPointer: false}, 135 | "Tags": {Type: "String", IsPointer: false}, 136 | }, 137 | }), 138 | 139 | Entry("File with one struct, fields are of unsupported slice type.", `package model 140 | 141 | type ( 142 | MyStruct struct { 143 | ID int 144 | Name string 145 | Items []map[string]int 146 | } 147 | )`, StructureList{ 148 | "MyStruct": { 149 | "ID": {Type: "int", IsPointer: false}, 150 | "Name": {Type: "string", IsPointer: false}, 151 | "unsupported_array_type_*ast.ArrayType": {Type: "*ast.MapType", IsPointer: false}, 152 | }, 153 | }), 154 | 155 | Entry("File with one struct, field is of pointer type.", `package model 156 | 157 | type ( 158 | MyStruct struct { 159 | ID int 160 | Name *string 161 | } 162 | )`, StructureList{ 163 | "MyStruct": { 164 | "ID": {Type: "int", IsPointer: false}, 165 | "Name": {Type: "string", IsPointer: true}, 166 | }, 167 | }), 168 | 169 | Entry("File with one struct, field is of types time.Time/*time.Time.", `package model 170 | 171 | type ( 172 | MyStruct struct { 173 | ID int 174 | Name string 175 | T time.Time 176 | PT *time.Time 177 | } 178 | )`, StructureList{ 179 | "MyStruct": { 180 | "ID": {Type: "int", IsPointer: false}, 181 | "Name": {Type: "string", IsPointer: false}, 182 | "T": {Type: "time.Time", IsPointer: false}, 183 | "PT": {Type: "time.Time", IsPointer: true}, 184 | }, 185 | }), 186 | 187 | Entry("File with one struct, field is of unsupported type.", `package model 188 | 189 | type ( 190 | MyStruct struct { 191 | I int 192 | F func() 193 | M map[int]string 194 | PM *map[int]string 195 | } 196 | )`, StructureList{ 197 | "MyStruct": { 198 | "I": {Type: "int", IsPointer: false}, 199 | "unsupported_*ast.FuncType": {Type: "*ast.FuncType", IsPointer: false}, 200 | "unsupported_*ast.MapType": {Type: "*ast.MapType", IsPointer: false}, 201 | "unsupported_star_expr_*ast.StarExpr": {Type: "*ast.MapType", IsPointer: false}, 202 | }, 203 | }), 204 | ) 205 | 206 | Describe("Lookup", func() { 207 | 208 | Context("when call Lookup with existing struct", func() { 209 | 210 | It("returns set of fields", func() { 211 | str, err := Parse("file.go", bytes.NewReader([]byte(`package model 212 | 213 | type ( 214 | MyStruct struct { 215 | ID int 216 | Name *string 217 | } 218 | )`))) 219 | Expect(err).NotTo(HaveOccurred()) 220 | 221 | fields, err := Lookup(str, "MyStruct") 222 | Expect(err).NotTo(HaveOccurred()) 223 | Expect(fields).To(Equal(Structure{ 224 | "ID": {Type: "int", IsPointer: false}, 225 | "Name": {Type: "string", IsPointer: true}, 226 | })) 227 | 228 | }) 229 | }) 230 | 231 | Context("when call Lookup with non-existing struct", func() { 232 | 233 | It("returns set of fields", func() { 234 | str, err := Parse("file.go", bytes.NewReader([]byte(`package model 235 | 236 | type ( 237 | MyStruct struct { 238 | ID int 239 | Name *string 240 | } 241 | )`))) 242 | Expect(err).NotTo(HaveOccurred()) 243 | 244 | fields, err := Lookup(str, "NotExists") 245 | Expect(err).To(MatchError(`structure "NotExists" not found`)) 246 | Expect(fields).To(BeNil()) 247 | }) 248 | }) 249 | }) 250 | 251 | }) 252 | -------------------------------------------------------------------------------- /source/source_suite_test.go: -------------------------------------------------------------------------------- 1 | package source_test 2 | 3 | import ( 4 | "testing" 5 | . "github.com/onsi/ginkgo/v2" 6 | . "github.com/onsi/gomega" 7 | ) 8 | 9 | func TestSource(t *testing.T) { 10 | RegisterFailHandler(Fail) 11 | RunSpecs(t, "Source Suite") 12 | } 13 | --------------------------------------------------------------------------------