{"id":18768435,"url":"https://github.com/ekhabarov/sts","last_synced_at":"2025-04-13T06:32:40.963Z","repository":{"id":49322587,"uuid":"245036698","full_name":"ekhabarov/sts","owner":"ekhabarov","description":"sts: struct to struct transformers generator.","archived":false,"fork":false,"pushed_at":"2023-09-14T22:12:50.000Z","size":57,"stargazers_count":24,"open_issues_count":2,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-06T07:54:47.044Z","etag":null,"topics":["automation","generator","go-generate","golang","hacktoberfest","identical-types","matcher","structures","tools"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ekhabarov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-05T00:55:41.000Z","updated_at":"2024-06-15T01:38:27.000Z","dependencies_parsed_at":"2024-06-19T06:10:37.400Z","dependency_job_id":"1bf16efd-a109-43dd-b0de-1eac191a9cc1","html_url":"https://github.com/ekhabarov/sts","commit_stats":null,"previous_names":["ekhabarov/sts","powerflyco/sts"],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekhabarov%2Fsts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekhabarov%2Fsts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekhabarov%2Fsts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ekhabarov%2Fsts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ekhabarov","download_url":"https://codeload.github.com/ekhabarov/sts/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248674677,"owners_count":21143760,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","generator","go-generate","golang","hacktoberfest","identical-types","matcher","structures","tools"],"created_at":"2024-11-07T19:12:39.188Z","updated_at":"2025-04-13T06:32:40.457Z","avatar_url":"https://github.com/ekhabarov.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sts: struct to struct: generator of transformation functions\n\n[![codecov](https://codecov.io/gh/powerflyco/sts/branch/master/graph/badge.svg)](https://codecov.io/gh/powerflyco/sts)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/powerflyco/sts)](https://github.com/powerflyco/sts/releases)\n[![Travis (.org)](https://img.shields.io/travis/powerflyco/sts)](https://travis-ci.org/powerflyco/sts)\n[![GoDoc](https://godoc.org/https://godoc.org/github.com/powerflyco/sts?status.svg)](https://godoc.org/github.com/powerflyco/sts)\n[![Go Report Card](https://goreportcard.com/badge/github.com/powerflyco/sts)](https://goreportcard.com/report/github.com/powerflyco/sts)\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Install](#install)\n* [Motivation](#motivation)\n* [Idea](#idea)\n  * [Other implementations.](#other-implementations)\n* [How](#how)\n  * [Step 1](#step-1)\n  * [Step 2](#step-2)\n    * [Example matcher](#example-matcher)\n      * [Int2Bool, NullsTime2TimeTimePtr wait, what?](#int2bool-nullstime2timetimeptr-wait-what)\n  * [Step 3](#step-3)\n* [go generate](#go-generate)\n* [License](#license)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n## Install\n\n```shell\ngo get -u github.com/powerflyco/sts/cmd/sts\n```\n\n## Motivation\nWorking on integration between one app and different APIs (most of them,\nfortunately, have Go clients) includes pretty much code which transforms one\nstructure into another, because for Go two structures with identical field set\nand identical types are different types. Identical types could be converted one\ninto another with simple conversion: `targetType(destType)`, but having\n[identical](https://golang.org/ref/spec#Type_identity) type is too rare case.\n\nThat means it's necessary to write such transformations manually, which is, from\none hand is tediously from another one is straightforward.\n\n## Idea\nThe idea is as simple as possible: produce set of functions which allow convert\none type into another.\n\nIt can be done within three steps:\n\n1. Source code analyze.\n1. Field type matching.\n1. Generations pair of functions: forward `SourceType2DestType` and reverse `DestType2SourceType`.\n\n### Other implementations.\nThere is a [plugin](http://github.com/bold-commerce/protoc-gen-struct-transformer) for Protobuf with the same idea.\n\n## How\n\n### Step 1\nOn first step `sts` have to obtain information about structures which will be\ninvolved into transformation process by analyzing source code files contained\nthese structures. To achieve this, packages [go/ast](https://golang.org/pkg/go/ast), [go/types](https://golang.org/pkg/go/types), etc., from\nstandard library can be used.\n\nUsing these packages `sts` builds a map with data types information. For details\nsee [parser.go](./parser.go).\n\n### Step 2\nInformation from previous step is passes to matcher. Matcher lookups two\nstructures by name (structures names are passed via CLI params, see examples\nbelow), source (left) and destination (right). Then it builds field pairs using\nnext rules:\n\n* field on the left structure with `sts` tag will be matched with field on right side by right-side field name equals to `sts` tag value.\n* if right-side field not found by name, then `sts` tag value will be compared with value of provided tag list.\n* any fields without `sts` or other source tags will be skipped.\n\n#### Example matcher\nLet's say we have two structures\n\n```go\ntype Source struct {\n\tI  int\n\tS  string\n\tI1 int        `sts:\"I64\"`\n\tI2 int        `sts:\"B\"`\n\tPT *time.Time `sts:\"Nt\"`\n\tJJ string     `sts:\"json_field\"`\n\tD  int32      `sts:\"db_field\"`\n}\n```\n\nand\n\n```go\ntype Dest struct {\n\tI         int\n\tS         string\n\tI64       int64\n\tB         bool\n\tNt        nulls.Time\n\tJsonField string `json:\"json_field\"`\n\tDB        int64  `db:\"db_field\"`\n}\n```\n\nafter run a command\n\n```shell\nsts -src /path/to/src.go:Source -dst /path/to/dst.go:Dest -o ./output -dt json,db\n```\n\nmatcher consider next combinations\n\n\nSource | Destination | Conversion              | Note\n-------|-------------|-------------------------|------\n `I`   | `--`        | `--`                    | source field has not tag\n `S`   | `--`        | `--`                    | source field has not tag\n `I1`  | `I64`       | direct                  | matched `sts` tag value and field name\n `I2`  | `B`         | `Int2Bool`              | matched `sts` tag value and field name\n `PT`  | `Nt`        | `NullsTime2TimeTimePtr` | matched `sts` tag value and field name\n`JJ`   | `JsonField` | none                    | matched `sts` tag value and `json` tag value. `json` tag passed via `-dt` CLI parameter.\n`DB`   | `D`         | direct                  | matched `sts` tag value and `db` tag value. `db` tag passed via `-dt` CLI parameter.\n\n\n##### Int2Bool, NullsTime2TimeTimePtr wait, what?\nMatcher uses type info provided by `go/types` package. When it compares field it\nalso checks paired field for [assignability](https://golang.org/pkg/go/types/#AssignableTo) and [convertibility](https://golang.org/pkg/go/types/#ConvertibleTo).\n* Assignability shows can one field be assigned to another without any conversion.\n* Convertibility shows can one field be directly converted to another one.\n\nBut in cases when fields in pair are not `assignable` and are not `convertable`,\nthe tool just generate conversion function with name of format\n\n```go\n\u003cSourceType\u003e2\u003cDestType\u003e\n// and\n\u003cDestType\u003e2\u003cSourceType\u003e\n```\n\nthat means it's necessary to write these helper functions manually. Fortunately,\nquantity of such function should be low. Number of examples can be found in\n[examples](./examples/output/helpers.go) package.\n\n### Step 3\nOn the last step `sts` creates a file with name `\u003csource\u003e_to_\u003cdest\u003e.sts.go` with\npair of ready-to-use functions for each pair of structures passed as a\nparameters to `sts`.\n\n```go\n// source_to_dest.sts.go\n\n// Code generated by sts v0.0.4-alpha-dev. DO NOT EDIT.\n\npackage output\n\nimport (\n\t\"github.com/powerflyco/sts/examples\"\n\t\"github.com/powerflyco/sts/examples/dest\"\n)\n\nfunc Source2Dest(src examples.Source) dest.Dest {\n\treturn dest.Dest{\n\t\tI64:       int64(src.I1),\n\t\tB:         Int2Bool(src.I2),\n\t\tNt:        TimeTimePtr2NullsTime(src.PT),\n\t\tJsonField: src.JJ,\n\t\tDB:        int64(src.D),\n\t}\n}\nfunc Dest2Source(src dest.Dest) examples.Source {\n\treturn examples.Source{\n\t\tI1: int(src.I64),\n\t\tI2: Bool2Int(src.B),\n\t\tPT: NullsTime2TimeTimePtr(src.Nt),\n\t\tJJ: src.JsonField,\n\t\tD:  int32(src.DB),\n\t}\n}\nfunc SourcePtr2DestPtr(src *examples.Source) *dest.Dest { /*...*/ }\nfunc DestPtr2SourcePtr(src *dest.Dest) *examples.Source { /*...*/ }\nfunc SourceList2DestList(src []examples.Source) []dest.Dest { /*...*/ }\nfunc DestList2SourceList(src []dest.Dest) []examples.Source { /*...*/ }\nfunc SourceList2DestPtrList(src []examples.Source) []*dest.Dest { /*...*/ }\nfunc DestPtrList2SourceList(src []*dest.Dest) []examples.Source { /*...*/ }\nfunc SourcePtrList2DestList(src []*examples.Source) []dest.Dest { /*...*/ }\nfunc DestList2SourcePtrList(src []dest.Dest) []*examples.Source { /*...*/ }\nfunc SourcePtrList2DestPtrList(src []*examples.Source) []*dest.Dest { /*...*/ }\nfunc DestPtrList2SourcePtrList(src []*dest.Dest) []*examples.Source { /*...*/ }\n```\n\nfull example see in [examples package](./examples).\n\n## go generate\nGo has a command `go generate` ([blog](https://blog.golang.org/generate)|[proposal](https://docs.google.com/document/d/1V03LUfjSADDooDMhe-_K59EgpTEm3V8uvQRuNMAEnjg/edit)).\nThis command allows to run tools mentioned in special comments in Go code, like\nthis:\n\n```go\n//go:generate sts -src $GOFILE:Source -dst $GOFILE:Dest -o ./output -dt json,db\ntype Source struct {\n\tI  int\n...\n```\n\nafter `go generate ./...` will be run, it in turn, will run `sts` tool with\ngiven parameters. `$GOFILE` variable will be replaced with a path to current\n`.go` file by `go generate` tool.\n\n\n## License\n\nMIT License\n\nCopyright (c) 2020 Evgeny Khabarov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekhabarov%2Fsts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fekhabarov%2Fsts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fekhabarov%2Fsts/lists"}