├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── THIRD_PARTY ├── cmd └── protoc-gen-xservice │ └── proto.go ├── docs └── DeveloperQuickstartGuide.md ├── framework ├── errors │ └── error.go ├── hooks │ └── hooks.go ├── server │ └── server.go ├── transport │ └── transport.go ├── xcontext │ └── ctx.go └── xhttp │ └── xhttp.go ├── generator └── proto │ └── go │ └── api.go ├── integration_tests └── api_hello_world │ ├── api_integration_test.go │ ├── gen.go │ ├── helloworld.pb.go │ ├── helloworld.proto │ └── helloworld.proto.go ├── internal ├── xgenerator │ └── types │ │ ├── anonymous_func.go │ │ ├── colsure.go │ │ ├── comment.go │ │ ├── const.go │ │ ├── const_test.go │ │ ├── file.go │ │ ├── func.go │ │ ├── func_test.go │ │ ├── genutil.go │ │ ├── genutil_test.go │ │ ├── go.go │ │ ├── go_test.go │ │ ├── goblock.go │ │ ├── init_struct.go │ │ ├── interface.go │ │ ├── interface_test.go │ │ ├── method.go │ │ ├── prototype.go │ │ ├── prototype_test.go │ │ ├── slice_literal.go │ │ ├── struct.go │ │ ├── struct_test.go │ │ ├── switch.go │ │ └── type_reference.go └── xproto │ ├── typesmap │ ├── testdata │ │ ├── fileset.pb │ │ ├── gen.go │ │ ├── importer.proto │ │ ├── public_importer.proto │ │ ├── public_reimporter.proto │ │ ├── root_pkg.proto │ │ └── service.proto │ ├── typesmap.go │ └── typesmap_test.go │ ├── version.go │ └── xprotoutil │ └── xprotoutil.go └── logo.png /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | **Description** 18 | 19 | 22 | 23 | **Steps to reproduce the issue:** 24 | 25 | 1. 26 | 2. 27 | 3. 28 | 29 | **Describe the results you received:** 30 | 31 | 32 | **Describe the results you expected:** 33 | 34 | 35 | **Additional information you deem important:** 36 | 37 | 38 | **XService Version:** 39 | 40 | 43 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | **- What I did** 11 | 12 | 13 | **- How I did it** 14 | 15 | 16 | **- How to verify it (e.g. test cases)** 17 | 18 | 19 | **- Description for the release changelog** 20 | 21 | 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | .idea 17 | 18 | /vendor 19 | 20 | /cmd/tmp/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.8.x 5 | - 1.9.x 6 | - tip 7 | 8 | before_install: 9 | - go get github.com/mattn/goveralls 10 | 11 | install: 12 | - go get -u github.com/golang/dep/... 13 | - dep ensure 14 | 15 | script: 16 | - go test -race -v ./... 17 | - $HOME/gopath/bin/goveralls -service=travis-ci -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at marcel.edmund.franke@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to xservice 2 | 3 | ## Reporting other issues 4 | 5 | Check that [our issue database](https://github.com/donutloop/xservice/issues) 6 | doesn't already include that problem or suggestion before submitting an issue. 7 | if you have ways to reproduce the issue or have additional information that may help 8 | resolving the issue, please leave a comment. This information will help us review and fix your issue faster. 9 | 10 | ## contribution guidelines 11 | 12 | ### Before you do changes 13 | 14 | Before contributing large or high impact changes, make the effort to coordinate 15 | with the maintainers of the project before submitting a pull request. This 16 | prevents you from doing extra work that may or may not be merged. 17 | 18 | Large PRs that are just submitted without any prior communication are unlikely 19 | to be successful. 20 | 21 | While pull requests are the methodology for submitting changes to code, changes 22 | are much more likely to be accepted if they are accompanied by additional 23 | engineering work. While we don't define this explicitly, most of these goals 24 | are accomplished through communication of the design goals and subsequent 25 | solutions. Often times, it helps to first state the problem before presenting 26 | solutions. 27 | 28 | Typically, the best methods of accomplishing this are to submit an issue, 29 | stating the problem. This issue can include a problem statement and a 30 | checklist with requirements. If solutions are proposed, alternatives should be 31 | listed and eliminated. Even if the criteria for elimination of a solution is 32 | frivolous, say so. 33 | 34 | Larger changes typically work best with design documents. These are focused on 35 | providing context to the design at the time the feature was conceived and can 36 | inform future documentation contributions. 37 | 38 | 39 | ### Design and cleanup proposals 40 | 41 | You can propose new designs for existing Toolkit features. You can also design 42 | entirely new features. We really appreciate contributors who want to refactor or 43 | otherwise cleanup our project. 44 | 45 | ### Conventions 46 | 47 | Fork the main branch and make changes on your fork in a feature branch: 48 | 49 | - If it's a bug fix branch, name it XXXX-something where XXXX is the number of the issue. 50 | - If it's a feature branch, create an enhancement issue to announce 51 | your intentions, and name it XXXX-something where XXXX is the number of the issue. 52 | 53 | Submit tests for your changes. 54 | 55 | Update the documentation when creating or modifying features. Test your 56 | documentation changes for clarity, concision, and correctness, as well as a 57 | clean documentation build. 58 | 59 | Write clean code. Universally formatted code promotes ease of writing, reading, 60 | and maintenance. Always run `gofmt -s -w file.go` on each changed file before 61 | committing your changes. Most editors have plug-ins that do this automatically. 62 | 63 | Pull request descriptions should be as clear as possible and include a reference 64 | to all the issues that they address. 65 | 66 | ### Commit Messages 67 | 68 | Commit messages must start with a capitalized and short summary (max. 50 chars) 69 | written in the imperative, followed by an optional, more detailed explanatory 70 | text which is separated from the summary by an empty line. 71 | 72 | Commit messages should follow best practices, including explaining the context 73 | of the problem and how it was solved, including in caveats or follow up changes 74 | required. They should tell the story of the change and provide readers 75 | understanding of what led to it. 76 | 77 | In practice, the best approach to maintaining a nice commit message is to 78 | leverage a `git add -p` and `git commit --amend` to formulate a solid 79 | changeset. This allows one to piece together a change, as information becomes 80 | available. 81 | 82 | ### Review 83 | 84 | Code review comments may be added to your pull request. Discuss, then make the 85 | suggested modifications and push additional commits to your feature branch. Post 86 | a comment after pushing. New commits show up in the pull request automatically, 87 | but the reviewers are notified only when you comment. 88 | 89 | Pull requests must be cleanly rebased on top of master. 90 | 91 | After every commit, make sure the test suite passes. Include 92 | documentation changes in the same pull request so that a revert would remove 93 | all traces of the feature or fix. 94 | 95 | Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in commits that 96 | close an issue. Including references automatically closes the issue on a merge. 97 | 98 | ### How can I become a maintainer? 99 | 100 | contacting the project leader at marcel.edmund.franke@gmail.com. 101 | 102 | ## Coding Style 103 | 104 | Unless explicitly stated, we follow all coding guidelines from the Go 105 | community. While some of these standards may seem arbitrary, they somehow seem 106 | to result in a solid, consistent codebase. 107 | 108 | The rules: 109 | 110 | * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). 111 | * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. 112 | * Make your changes as you see fit ensuring that you create appropriate tests along with your changes. Test your changes as you go. 113 | * Pull requests need to be based on and opened against the `master` branch. 114 | * Commit messages should be prefixed with the sub package(s) they modify. 115 | * E.g. "schedule: make scheduling faster" 116 | * All code should pass the default levels of [`golint`](https://github.com/golang/lint). 117 | * Follow the [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). 118 | * Document _all_ declarations and methods, even private ones. Declare 119 | expectations, caveats and anything else that may be important. If a type 120 | gets exported, having the comments already there will ensure it's ready. 121 | * All tests should run with `go test` and outside tooling should not be 122 | required. No, we don't need another unit testing framework. Assertion 123 | packages are acceptable if they provide _real_ incremental value. 124 | 125 | 126 | ## Copyright 127 | 128 | New files that you contribute should use the standard copyright header: 129 | 130 | ``` 131 | // Copyright 2018 XService, All Rights Reserved. 132 | // 133 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 134 | // use this file except in compliance with the License. A copy of the License is 135 | // located at 136 | // 137 | // http://www.apache.org/licenses/LICENSE-2.0 138 | // 139 | // or in the "license" file accompanying this file. This file is distributed on 140 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 141 | // express or implied. See the License for the specific language governing 142 | // permissions and limitations under the License. 143 | ``` 144 | 145 | Exceptions are example and doc files 146 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/davecgh/go-spew" 6 | packages = ["spew"] 7 | revision = "346938d642f2ec3594ed81d874461961cd0faa76" 8 | version = "v1.1.0" 9 | 10 | [[projects]] 11 | name = "github.com/gogo/protobuf" 12 | packages = [ 13 | "jsonpb", 14 | "proto", 15 | "sortkeys", 16 | "types" 17 | ] 18 | revision = "1adfc126b41513cc696b209667c8656ea7aac67c" 19 | version = "v1.0.0" 20 | 21 | [[projects]] 22 | name = "github.com/golang/protobuf" 23 | packages = [ 24 | "jsonpb", 25 | "proto", 26 | "protoc-gen-go/descriptor", 27 | "protoc-gen-go/plugin", 28 | "ptypes/struct" 29 | ] 30 | revision = "925541529c1fa6821df4e44ce2723319eb2be768" 31 | version = "v1.0.0" 32 | 33 | [[projects]] 34 | name = "github.com/pkg/errors" 35 | packages = ["."] 36 | revision = "645ef00459ed84a119197bfb8d8205042c6df63d" 37 | version = "v0.8.0" 38 | 39 | [[projects]] 40 | name = "github.com/pmezard/go-difflib" 41 | packages = ["difflib"] 42 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 43 | version = "v1.0.0" 44 | 45 | [[projects]] 46 | name = "github.com/stretchr/testify" 47 | packages = [ 48 | "assert", 49 | "require" 50 | ] 51 | revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" 52 | version = "v1.2.1" 53 | 54 | [[projects]] 55 | branch = "master" 56 | name = "golang.org/x/tools" 57 | packages = [ 58 | "go/ast/astutil", 59 | "imports" 60 | ] 61 | revision = "ce871d178848e3eea1e8795e5cfb74053dde4bb9" 62 | 63 | [solve-meta] 64 | analyzer-name = "dep" 65 | analyzer-version = 1 66 | inputs-digest = "f7bee24ddedbee3fc3529d34e2f2e233697578c8c3d19a24f1f5a8c6fb9ca422" 67 | solver-name = "gps-cdcl" 68 | solver-version = 1 69 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/golang/protobuf" 30 | version = "1.0.0" 31 | 32 | [[constraint]] 33 | name = "github.com/pkg/errors" 34 | version = "0.8.0" 35 | 36 | [[constraint]] 37 | branch = "master" 38 | name = "golang.org/x/tools" 39 | 40 | [prune] 41 | go-tests = true 42 | unused-packages = true 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | 3 | ALL_PACKAGES=$(shell go list ./... | grep -v "vendor") 4 | 5 | setup: 6 | go get -u github.com/golang/lint/golint 7 | 8 | fmt: 9 | go fmt $(ALL_PACKAGES) 10 | 11 | vet: 12 | go vet $(ALL_PACKAGES) 13 | 14 | lint: 15 | @for p in $(ALL_PACKAGES); do \ 16 | echo "==> Linting $$p"; \ 17 | golint -set_exit_status $$p; \ 18 | done 19 | 20 | test: fmt vet 21 | go install -v ./cmd/protoc-gen-xservice 22 | go generate ./integration_tests/api_hello_world 23 | ENVIRONMENT=test go test -v $(ALL_PACKAGES) 24 | 25 | test-cover-html: 26 | @echo "mode: count" > coverage-all.out 27 | 28 | $(foreach pkg, $(ALL_PACKAGES),\ 29 | ENVIRONMENT=test go test -coverprofile=coverage.out -covermode=count $(pkg);\ 30 | tail -n +2 coverage.out >> coverage-all.out;) 31 | go tool cover -html=coverage-all.out -o out/coverage.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![alt text](logo.png "XService") 3 | 4 | [![Build Status](https://travis-ci.org/donutloop/xservice.svg?branch=master)](https://travis-ci.org/donutloop/xservice) 5 | [![Coverage Status](https://coveralls.io/repos/github/donutloop/xservice/badge.svg)](https://coveralls.io/github/donutloop/xservice) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/donutloop/xservice)](https://goreportcard.com/report/github.com/donutloop/xservice) 7 | ## Introduction 8 | 9 | xservice is a simple generator library used for generating services quickly and easily for an proto buffer. 10 | The purpose of this project is to generate of a lot of the basic boilerplate associated with writing API services so that you can focus on writing business logic. 11 | 12 | ## Making a golang xservice 13 | 14 | To make a golang service: 15 | 16 | 1. Define your service in a **Proto** file. 17 | 2. Use the `protoc` command to generate go code from the **Proto** file, it 18 | will generate an **interface**, a **server** and some **server utils** (to 19 | easily start an http listener). 20 | 3. Implement the generated **interface** to implement the service. 21 | 22 | For example, a HelloWorld **Proto** file: 23 | 24 | ```protobuf 25 | syntax = "proto3"; 26 | package donutloop.xservice.example.helloworld; 27 | option go_package = "helloworld"; 28 | 29 | service HelloWorld { 30 | rpc Hello(HelloReq) returns (HelloResp); 31 | } 32 | 33 | message HelloReq { 34 | string subject = 1; 35 | } 36 | 37 | message HelloResp { 38 | string text = 1; 39 | } 40 | ``` 41 | 42 | From which xservice can auto-generate this **interface** (running the `protoc` command): 43 | 44 | ```go 45 | type HelloWorld interface { 46 | Hello(context.Context, *HelloReq) (*HelloResp, error) 47 | } 48 | ``` 49 | 50 | You provide the **implementation**: 51 | 52 | ```go 53 | package main 54 | 55 | import ( 56 | "context" 57 | "net/http" 58 | 59 | pb "github.com/donutloop/xservice-example/helloworld" 60 | ) 61 | 62 | type HelloWorldServer struct{} 63 | 64 | func (s *HelloWorldServer) Hello(ctx context.Context, req *pb.HelloReq) (*pb.HelloResp, error) { 65 | return &pb.HelloResp{Text: "Hello " + req.Subject}, nil 66 | } 67 | 68 | // Run the implementation in a local server 69 | func main() { 70 | handler := pb.NewHelloWorldServer(&HelloWorldServer{}, nil) 71 | // You can use any mux you like - NewHelloWorldServer gives you an http.Handler. 72 | mux := http.NewServeMux() 73 | // The generated code includes a const, PathPrefix, which 74 | // can be used to mount your service on a mux. 75 | mux.Handle(pb.HelloWorldPathPrefix, handler) 76 | http.ListenAndServe(":8080", mux) 77 | } 78 | ``` 79 | 80 | Now you can just use the auto-generated JSON or Protobuffer Client to make remote calls to your new service: 81 | 82 | ##### JSON 83 | ```go 84 | package main 85 | 86 | import ( 87 | "context" 88 | "fmt" 89 | "net/http" 90 | 91 | pb "github.com/donutloop/xservice-example/helloworld" 92 | ) 93 | 94 | func main() { 95 | client := pb.NewHelloWorldJSONClient("http://localhost:8080", &http.Client{}) 96 | 97 | resp, err := client.Hello(context.Background(), &pb.HelloReq{Subject: "World"}) 98 | if err == nil { 99 | fmt.Println(resp.Text) // prints "Hello World" 100 | } 101 | } 102 | ``` 103 | 104 | ##### Protobuffer 105 | 106 | ```go 107 | package main 108 | 109 | import ( 110 | "context" 111 | "fmt" 112 | "net/http" 113 | 114 | pb "github.com/donutloop/xservice-example/helloworld" 115 | ) 116 | 117 | func main() { 118 | client := pb.NewHelloWorldProtobufferClient("http://localhost:8080", &http.Client{}) 119 | 120 | resp, err := client.Hello(context.Background(), &pb.HelloReq{Subject: "World"}) 121 | if err == nil { 122 | fmt.Println(resp.Text) // prints "Hello World" 123 | } 124 | } 125 | ``` 126 | 127 | ## QuickStart for developers 128 | 129 | Please refer [**docs/DeveloperQuickStart.md**](https://github.com/donutloop/xservice/blob/master/docs/DeveloperQuickstartGuide.md) 130 | 131 | ## Roadmap 132 | 133 | * Multi language support 134 | 135 | ## Contribution 136 | 137 | Thank you for considering to help out with the source code! We welcome contributions from 138 | anyone on the internet, and are grateful for even the smallest of fixes! 139 | 140 | If you'd like to contribute to xservice, please fork, fix, commit and send a pull request 141 | for the maintainers to review and merge into the main code base to ensure those changes are in line with the general philosophy of the project and/or get some 142 | early feedback which can make both your efforts much lighter as well as our review and merge 143 | procedures quick and simple. 144 | 145 | Please read and follow our [Contributing](https://github.com/donutloop/xservice/blob/master/CONTRIBUTING.md). 146 | 147 | ## Code of Conduct 148 | 149 | Please read and follow our [Code of Conduct](https://github.com/donutloop/xservice/blob/master/CODE_OF_CONDUCT.md). 150 | 151 | ## Credits 152 | 153 | * Parts of xservice thinking comes from twirp (https://github.com/twitchtv/twirp) -------------------------------------------------------------------------------- /THIRD_PARTY: -------------------------------------------------------------------------------- 1 | Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. All rights reserved. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright [yyyy] [name of copyright owner] 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. -------------------------------------------------------------------------------- /cmd/protoc-gen-xservice/proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. All rights reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package main 19 | 20 | import ( 21 | plugin "github.com/golang/protobuf/protoc-gen-go/plugin" 22 | "io" 23 | "io/ioutil" 24 | "os" 25 | 26 | "github.com/donutloop/xservice/generator/proto/go" 27 | "github.com/gogo/protobuf/proto" 28 | "log" 29 | ) 30 | 31 | func main() { 32 | g := goproto.NewAPIGenerator() 33 | Main(g) 34 | } 35 | 36 | type Generator interface { 37 | Generate(in *plugin.CodeGeneratorRequest) (*plugin.CodeGeneratorResponse, error) 38 | } 39 | 40 | func Main(g Generator) { 41 | req := readGenRequest(os.Stdin) 42 | resp, err := g.Generate(req) 43 | if err != nil { 44 | log.Fatal(err) 45 | } 46 | writeResponse(os.Stdout, resp) 47 | } 48 | 49 | func readGenRequest(r io.Reader) *plugin.CodeGeneratorRequest { 50 | data, err := ioutil.ReadAll(os.Stdin) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | req := new(plugin.CodeGeneratorRequest) 56 | if err = proto.Unmarshal(data, req); err != nil { 57 | log.Fatal(err) 58 | } 59 | 60 | if len(req.FileToGenerate) == 0 { 61 | log.Fatal("no files to generate") 62 | } 63 | 64 | return req 65 | } 66 | 67 | func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) { 68 | data, err := proto.Marshal(resp) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | _, err = w.Write(data) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /docs/DeveloperQuickstartGuide.md: -------------------------------------------------------------------------------- 1 | # xservice Developer Quickstart Guide 2 | 3 | ## Requirements 4 | 5 | * Go 1.8+ 6 | * protoc, the protobuf compiler. You need version 3+. 7 | * github.com/golang/protobuf/protoc-gen-go, the Go protobuf generator plugin. Get this with go get. 8 | 9 | ## Godep 10 | 11 | Use dep (https://github.com/golang/dep) to add/update dependencies. 12 | 13 | As we don't commit vendor into our release code! 14 | 15 | ## Prepare GO development environment 16 | 17 | Follow https://golang.org/doc/install to install golang. 18 | Make sure you have your $GOPATH, $PATH setup correctly 19 | 20 | ## Clone xservice code 21 | 22 | Clone or copy the code into $GOPATH/src/github.com/donutloop/xservice 23 | 24 | ## install xservice 25 | 26 | ```bash 27 | $ go get -u github.com/golang/dep/cmd/dep 28 | 29 | # Download deps 30 | dep ensure 31 | 32 | cd $GOPATH/src/github.com/donutloop/xservice 33 | $ go install -v ./... 34 | ``` 35 | 36 | ## Run xservice 37 | 38 | ```bash 39 | $ cd $PROJECT 40 | $ protoc -I . service.proto --xservice_out=. --go_out=. 41 | ``` 42 | 43 | ## Test xservice 44 | 45 | ```bash 46 | $ make test 47 | ``` 48 | -------------------------------------------------------------------------------- /framework/errors/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package errors 19 | 20 | import ( 21 | "fmt" 22 | "net/http" 23 | ) 24 | 25 | // Error represents an error in a service call. 26 | type Error interface { 27 | // Code is of the valid error codes. 28 | Code() ErrorCode 29 | 30 | // Msg returns a human-readable, unstructured messages describing the error. 31 | Msg() string 32 | 33 | // WithMeta returns a copy of the Error with the given key-value pair attached 34 | // as metadata. If the key is already set, it is overwritten. 35 | WithMeta(key string, val string) Error 36 | 37 | // Meta returns the stored value for the given key. If the key has no set 38 | // value, Meta returns an empty string. There is no way to distinguish between 39 | // an unset value and an explicit empty string. 40 | Meta(key string) string 41 | 42 | // MetaMap returns the complete key-value metadata map stored on the error. 43 | MetaMap() map[string]string 44 | 45 | // Error returns a string of the form "error : " 46 | Error() string 47 | } 48 | 49 | // NewError is the generic constructor for a Error. The ErrorCode must be 50 | // one of the valid predefined constants, otherwise it will be converted to an 51 | // error {type: Internal, msg: "invalid error type {{code}}"}. If you need to 52 | // add metadata, use .WithMeta(key, value) method after building the error. 53 | func NewError(code ErrorCode, msg string) Error { 54 | if IsValidErrorCode(code) { 55 | return &twerr{ 56 | code: code, 57 | msg: msg, 58 | } 59 | } 60 | return &twerr{ 61 | code: Internal, 62 | msg: "invalid error type " + string(code), 63 | } 64 | } 65 | 66 | // NotFoundError constructor for the common NotFound error. 67 | func NotFoundError(msg string) Error { 68 | return NewError(NotFound, msg) 69 | } 70 | 71 | // InvalidArgumentError constructor for the common InvalidArgument error. Can be 72 | // used when an argument has invalid format, is a number out of range, is a bad 73 | // option, etc). 74 | func InvalidArgumentError(argument string, validationMsg string) Error { 75 | err := NewError(InvalidArgument, argument+" "+validationMsg) 76 | err = err.WithMeta("argument", argument) 77 | return err 78 | } 79 | 80 | // RequiredArgumentError is a more specific constructor for InvalidArgument 81 | // error. Should be used when the argument is required (expected to have a 82 | // non-zero value). 83 | func RequiredArgumentError(argument string) Error { 84 | return InvalidArgumentError(argument, "is required") 85 | } 86 | 87 | // InternalError constructor for the common Internal error. Should be used to 88 | // specify that something bad or unexpected happened. 89 | func InternalError(msg string) Error { 90 | return NewError(Internal, msg) 91 | } 92 | 93 | // InternalErrorWith is an easy way to wrap another error. It adds the 94 | // underlying error's type as metadata with a key of "cause", which can be 95 | // useful for debugging. Should be used in the common case of an unexpected 96 | // error returned from another API, but sometimes it is better to build a more 97 | // specific error (like with NewError(Unknown, err.Error()), for example). 98 | // 99 | // The returned error also has a Cause() method which will return the original 100 | // error, if it is known. This can be used with the github.com/pkg/errors 101 | // package to extract the root cause of an error. Information about the root 102 | // cause of an error is lost when it is serialized, so this doesn't let a client 103 | // know the exact root cause of a server's error. 104 | func InternalErrorWith(err error) Error { 105 | msg := err.Error() 106 | terr := NewError(Internal, msg) 107 | terr = terr.WithMeta("cause", fmt.Sprintf("%T", err)) // to easily tell apart wrapped internal errors from explicit ones 108 | return &WrappedErr{ 109 | wrapper: terr, 110 | cause: err, 111 | } 112 | } 113 | 114 | // ErrorCode represents a error type. 115 | type ErrorCode string 116 | 117 | // Valid error types. Most error types are equivalent to gRPC status codes 118 | // and follow the same semantics. 119 | const ( 120 | // Canceled indicates the operation was cancelled (typically by the caller). 121 | Canceled ErrorCode = "canceled" 122 | 123 | // Unknown error. For example when handling errors raised by APIs that do not 124 | // return enough error information. 125 | Unknown ErrorCode = "unknown" 126 | 127 | // InvalidArgument indicates client specified an invalid argument. It 128 | // indicates arguments that are problematic regardless of the state of the 129 | // system (i.e. a malformed file name, required argument, number out of range, 130 | // etc.). 131 | InvalidArgument ErrorCode = "invalid_argument" 132 | 133 | // DeadlineExceeded means operation expired before completion. For operations 134 | // that change the state of the system, this error may be returned even if the 135 | // operation has completed successfully (timeout). 136 | DeadlineExceeded ErrorCode = "deadline_exceeded" 137 | 138 | // NotFound means some requested entity was not found. 139 | NotFound ErrorCode = "not_found" 140 | 141 | // BadRoute means that the requested URL path wasn't routable to a 142 | // service and method. This is returned by the generated server, and usually 143 | // shouldn't be returned by applications. Instead, applications should use 144 | // NotFound or Unimplemented. 145 | BadRoute ErrorCode = "bad_route" 146 | 147 | // AlreadyExists means an attempt to create an entity failed because one 148 | // already exists. 149 | AlreadyExists ErrorCode = "already_exists" 150 | 151 | // PermissionDenied indicates the caller does not have permission to execute 152 | // the specified operation. It must not be used if the caller cannot be 153 | // identified (Unauthenticated). 154 | PermissionDenied ErrorCode = "permission_denied" 155 | 156 | // Unauthenticated indicates the request does not have valid authentication 157 | // credentials for the operation. 158 | Unauthenticated ErrorCode = "unauthenticated" 159 | 160 | // ResourceExhausted indicates some resource has been exhausted, perhaps a 161 | // per-user quota, or perhaps the entire file system is out of space. 162 | ResourceExhausted ErrorCode = "resource_exhausted" 163 | 164 | // FailedPrecondition indicates operation was rejected because the system is 165 | // not in a state required for the operation's execution. For example, doing 166 | // an rmdir operation on a directory that is non-empty, or on a non-directory 167 | // object, or when having conflicting read-modify-write on the same resource. 168 | FailedPrecondition ErrorCode = "failed_precondition" 169 | 170 | // Aborted indicates the operation was aborted, typically due to a concurrency 171 | // issue like sequencer check failures, transaction aborts, etc. 172 | Aborted ErrorCode = "aborted" 173 | 174 | // OutOfRange means operation was attempted past the valid range. For example, 175 | // seeking or reading past end of a paginated collection. 176 | // 177 | // Unlike InvalidArgument, this error indicates a problem that may be fixed if 178 | // the system state changes (i.e. adding more items to the collection). 179 | // 180 | // There is a fair bit of overlap between FailedPrecondition and OutOfRange. 181 | // We recommend using OutOfRange (the more specific error) when it applies so 182 | // that callers who are iterating through a space can easily look for an 183 | // OutOfRange error to detect when they are done. 184 | OutOfRange ErrorCode = "out_of_range" 185 | 186 | // Unimplemented indicates operation is not implemented or not 187 | // supported/enabled in this service. 188 | Unimplemented ErrorCode = "unimplemented" 189 | 190 | // Internal errors. When some invariants expected by the underlying system 191 | // have been broken. In other words, something bad happened in the library or 192 | // backend service. Do not confuse with HTTP Internal Server Error; an 193 | // Internal error could also happen on the client code, i.e. when parsing a 194 | // server response. 195 | Internal ErrorCode = "internal" 196 | 197 | // Unavailable indicates the service is currently unavailable. This is a most 198 | // likely a transient condition and may be corrected by retrying with a 199 | // backoff. 200 | Unavailable ErrorCode = "unavailable" 201 | 202 | // DataLoss indicates unrecoverable data loss or corruption. 203 | DataLoss ErrorCode = "data_loss" 204 | 205 | // NoError is the zero-value, is considered an empty error and should not be 206 | // used. 207 | NoError ErrorCode = "" 208 | ) 209 | 210 | // ServerHTTPStatusFromErrorCode maps a error type into a similar HTTP 211 | // response status. It is used by the server handler to set the HTTP 212 | // response status code. Returns 0 if the ErrorCode is invalid. 213 | func ServerHTTPStatusFromErrorCode(code ErrorCode) int { 214 | switch code { 215 | case Canceled: 216 | return http.StatusRequestTimeout 217 | case Unknown: 218 | return http.StatusInternalServerError 219 | case InvalidArgument: 220 | return http.StatusBadRequest 221 | case DeadlineExceeded: 222 | return http.StatusRequestTimeout 223 | case NotFound: 224 | return http.StatusNotFound 225 | case BadRoute: 226 | return http.StatusNotFound 227 | case AlreadyExists: 228 | return http.StatusConflict 229 | case PermissionDenied: 230 | return http.StatusForbidden 231 | case Unauthenticated: 232 | return http.StatusUnauthorized 233 | case ResourceExhausted: 234 | return http.StatusForbidden 235 | case FailedPrecondition: 236 | return http.StatusPreconditionFailed 237 | case Aborted: 238 | return http.StatusConflict 239 | case OutOfRange: 240 | return http.StatusBadRequest 241 | case Unimplemented: 242 | return http.StatusNotImplemented 243 | case Internal: 244 | return http.StatusInternalServerError 245 | case Unavailable: 246 | return http.StatusServiceUnavailable 247 | case DataLoss: 248 | return http.StatusInternalServerError 249 | case NoError: 250 | return http.StatusOK 251 | default: 252 | return 0 // Invalid! 253 | } 254 | } 255 | 256 | // IsValidErrorCode returns true if is one of the valid predefined constants. 257 | func IsValidErrorCode(code ErrorCode) bool { 258 | return ServerHTTPStatusFromErrorCode(code) != 0 259 | } 260 | 261 | // .Error implementation 262 | type twerr struct { 263 | code ErrorCode 264 | msg string 265 | meta map[string]string 266 | } 267 | 268 | func (e *twerr) Code() ErrorCode { return e.code } 269 | func (e *twerr) Msg() string { return e.msg } 270 | 271 | func (e *twerr) Meta(key string) string { 272 | if e.meta != nil { 273 | return e.meta[key] // also returns "" if key is not in meta map 274 | } 275 | return "" 276 | } 277 | 278 | func (e *twerr) WithMeta(key string, value string) Error { 279 | newErr := &twerr{ 280 | code: e.code, 281 | msg: e.msg, 282 | meta: make(map[string]string, len(e.meta)), 283 | } 284 | for k, v := range e.meta { 285 | newErr.meta[k] = v 286 | } 287 | newErr.meta[key] = value 288 | return newErr 289 | } 290 | 291 | func (e *twerr) MetaMap() map[string]string { 292 | return e.meta 293 | } 294 | 295 | func (e *twerr) Error() string { 296 | return fmt.Sprintf(" error %s: %s", e.code, e.msg) 297 | } 298 | 299 | // wrappedErr fulfills the .Error interface and the 300 | // github.com/pkg/errors.Causer interface. It exposes all the error 301 | // methods, but root cause of an error can be retrieved with 302 | // (*wrappedErr).Cause. This is expected to be used with the InternalErrorWith 303 | // function. 304 | type WrappedErr struct { 305 | wrapper Error 306 | cause error 307 | } 308 | 309 | func (e *WrappedErr) Code() ErrorCode { return e.wrapper.Code() } 310 | func (e *WrappedErr) Msg() string { return e.wrapper.Msg() } 311 | func (e *WrappedErr) Meta(key string) string { return e.wrapper.Meta(key) } 312 | func (e *WrappedErr) MetaMap() map[string]string { return e.wrapper.MetaMap() } 313 | func (e *WrappedErr) Error() string { return e.wrapper.Error() } 314 | func (e *WrappedErr) WithMeta(key string, val string) Error { 315 | return &WrappedErr{ 316 | wrapper: e.wrapper.WithMeta(key, val), 317 | cause: e.cause, 318 | } 319 | } 320 | func (e *WrappedErr) Cause() error { return e.cause } 321 | 322 | // wrappedError implements the github.com/pkg/errors.Causer interface, allowing errors to be 323 | // examined for their root cause. 324 | type wrappedError struct { 325 | msg string 326 | cause error 327 | } 328 | 329 | func WrapErr(err error, msg string) error { return &wrappedError{msg: msg, cause: err} } 330 | func (e *wrappedError) Cause() error { return e.cause } 331 | func (e *wrappedError) Error() string { return e.msg + ": " + e.cause.Error() } 332 | 333 | // ClientError adds consistency to errors generated in the client 334 | func ClientError(desc string, err error) Error { 335 | return InternalErrorWith(WrapErr(err, desc)) 336 | } 337 | 338 | // badRouteError is used when the twirp server cannot route a request 339 | func BadRouteError(msg string, method, url string) Error { 340 | err := NewError(BadRoute, msg) 341 | err = err.WithMeta("xservice_invalid_route", method+" "+url) 342 | return err 343 | } 344 | -------------------------------------------------------------------------------- /framework/hooks/hooks.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package hooks 19 | 20 | import ( 21 | "context" 22 | "github.com/donutloop/xservice/framework/errors" 23 | ) 24 | 25 | // ServerHooks is a container for callbacks that can instrument a 26 | // -generated server. These callbacks all accept a context and return a 27 | // context. They can use this to add to the request context as it threads 28 | // through the system, appending values or deadlines to it. 29 | // 30 | // The RequestReceived and RequestRouted hooks are special: they can return 31 | // errors. If they return a non-nil error, handling for that request will be 32 | // stopped at that point. The Error hook will be triggered, and the error will 33 | // be sent to the client. This can be used for stuff like auth checks before 34 | // deserializing a request. 35 | // 36 | // The RequestReceived hook is always called first, and it is called for every 37 | // request that the server handles. The last hook to be called in a 38 | // request's lifecycle is always ResponseSent, even in the case of an error. 39 | // 40 | // Details on the timing of each hook are documented as comments on the fields 41 | // of the ServerHooks type. 42 | type ServerHooks struct { 43 | // RequestReceived is called as soon as a request enters the 44 | // server at the earliest available moment. 45 | RequestReceived func(context.Context) (context.Context, error) 46 | 47 | // RequestRouted is called when a request has been routed to a 48 | // particular method of the server. 49 | RequestRouted func(context.Context) (context.Context, error) 50 | 51 | // ResponsePrepared is called when a request has been handled and a 52 | // response is ready to be sent to the client. 53 | ResponsePrepared func(context.Context) context.Context 54 | 55 | // ResponseSent is called when all bytes of a response (including an error 56 | // response) have been written. Because the ResponseSent hook is terminal, it 57 | // does not return a context. 58 | ResponseSent func(context.Context) 59 | 60 | // Error hook is called when a request responds with an Error, 61 | // either by the service implementation or by itself. 62 | // The Error is passed as argument to the hook. 63 | Error func(context.Context, errors.Error) context.Context 64 | } 65 | 66 | // ChainHooks creates a new *ServerHooks which chains the callbacks in 67 | // each of the constituent hooks passed in. Each hook function will be 68 | // called in the order of the ServerHooks values passed in. 69 | // 70 | // For the erroring hooks, RequestReceived and RequestRouted, any returned 71 | // errors prevent processing by later hooks. 72 | func ChainHooks(hooks ...*ServerHooks) *ServerHooks { 73 | if len(hooks) == 0 { 74 | return nil 75 | } 76 | if len(hooks) == 1 { 77 | return hooks[0] 78 | } 79 | 80 | return &ServerHooks{ 81 | RequestReceived: func(ctx context.Context) (context.Context, error) { 82 | var err error 83 | for _, h := range hooks { 84 | if h != nil && h.RequestReceived != nil { 85 | ctx, err = h.RequestReceived(ctx) 86 | if err != nil { 87 | return ctx, err 88 | } 89 | } 90 | } 91 | return ctx, nil 92 | }, 93 | RequestRouted: func(ctx context.Context) (context.Context, error) { 94 | var err error 95 | for _, h := range hooks { 96 | if h != nil && h.RequestRouted != nil { 97 | ctx, err = h.RequestRouted(ctx) 98 | if err != nil { 99 | return ctx, err 100 | } 101 | } 102 | } 103 | return ctx, nil 104 | }, 105 | ResponsePrepared: func(ctx context.Context) context.Context { 106 | for _, h := range hooks { 107 | if h != nil && h.ResponsePrepared != nil { 108 | ctx = h.ResponsePrepared(ctx) 109 | } 110 | } 111 | return ctx 112 | }, 113 | ResponseSent: func(ctx context.Context) { 114 | for _, h := range hooks { 115 | if h != nil && h.ResponseSent != nil { 116 | h.ResponseSent(ctx) 117 | } 118 | } 119 | }, 120 | Error: func(ctx context.Context, err errors.Error) context.Context { 121 | for _, h := range hooks { 122 | if h != nil && h.Error != nil { 123 | ctx = h.Error(ctx, err) 124 | } 125 | } 126 | return ctx 127 | }, 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /framework/server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package server 19 | 20 | import "net/http" 21 | 22 | type Server interface { 23 | http.Handler 24 | // ServiceDescriptor returns gzipped bytes describing the .proto file that 25 | // this service was generated from. Once unzipped, the bytes can be 26 | // unmarshalled as a github.com/golang/protobuf/protoc-gen-go/descriptor.FileDescriptorProto. 27 | ServiceDescriptor() ([]byte, int) 28 | // ProtocGenTwirpVersion is the semantic version string of the version of xservice 29 | ProtocGenXServiceVersion() string 30 | } 31 | -------------------------------------------------------------------------------- /framework/xcontext/ctx.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package xcontext 19 | 20 | import ( 21 | "context" 22 | "github.com/pkg/errors" 23 | "net/http" 24 | "strconv" 25 | ) 26 | 27 | type contextKey int 28 | 29 | const ( 30 | MethodNameKey contextKey = 1 + iota 31 | ServiceNameKey 32 | PackageNameKey 33 | StatusCodeKey 34 | RequestHeaderKey 35 | ResponseWriterKey 36 | ) 37 | 38 | func WithMethodName(ctx context.Context, name string) context.Context { 39 | return context.WithValue(ctx, MethodNameKey, name) 40 | } 41 | 42 | func WithServiceName(ctx context.Context, name string) context.Context { 43 | return context.WithValue(ctx, ServiceNameKey, name) 44 | } 45 | 46 | func WithPackageName(ctx context.Context, name string) context.Context { 47 | return context.WithValue(ctx, PackageNameKey, name) 48 | } 49 | 50 | func WithStatusCode(ctx context.Context, code int) context.Context { 51 | return context.WithValue(ctx, StatusCodeKey, strconv.Itoa(code)) 52 | } 53 | 54 | func WithResponseWriter(ctx context.Context, w http.ResponseWriter) context.Context { 55 | return context.WithValue(ctx, ResponseWriterKey, w) 56 | } 57 | 58 | // MethodName extracts the name of the method being handled in the given 59 | // context. If it is not known, it returns ("", false). 60 | func MethodName(ctx context.Context) (string, bool) { 61 | name, ok := ctx.Value(MethodNameKey).(string) 62 | return name, ok 63 | } 64 | 65 | // ServiceName extracts the name of the service handling the given context. If 66 | // it is not known, it returns ("", false). 67 | func ServiceName(ctx context.Context) (string, bool) { 68 | name, ok := ctx.Value(ServiceNameKey).(string) 69 | return name, ok 70 | } 71 | 72 | // PackageName extracts the fully-qualified protobuf package name of the service 73 | // handling the given context. If it is not known, it returns ("", false). If 74 | // the service comes from a proto file that does not declare a package name, it 75 | // returns ("", true). 76 | // 77 | // Note that the protobuf package name can be very different than the go package 78 | // name; the two are unrelated. 79 | func PackageName(ctx context.Context) (string, bool) { 80 | name, ok := ctx.Value(PackageNameKey).(string) 81 | return name, ok 82 | } 83 | 84 | // StatusCode retrieves the status code of the response (as string like "200"). 85 | // If it is known returns (status, true). 86 | // If it is not known, it returns ("", false). 87 | func StatusCode(ctx context.Context) (string, bool) { 88 | code, ok := ctx.Value(StatusCodeKey).(string) 89 | return code, ok 90 | } 91 | 92 | // WithHTTPRequestHeaders stores an http.Header in a context.Context. When 93 | // using a generated client, you can pass the returned context 94 | // into any of the request methods, and the stored header will be 95 | // included in outbound HTTP requests. 96 | // 97 | // This can be used to set custom HTTP headers like authorization tokens or 98 | // client IDs. But note that HTTP headers are a implementation detail, 99 | // only visible by middleware, not by the server implementation. 100 | // 101 | // WithHTTPRequestHeaders returns an error if the provided http.Header 102 | // would overwrite a header that is needed by, like "Content-Type". 103 | func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) { 104 | if _, ok := h["Content-Type"]; ok { 105 | return nil, errors.New("provided header cannot set Content-Type") 106 | } 107 | if _, ok := h["XService-Version"]; ok { 108 | return nil, errors.New("provided header cannot set Xservice-Version") 109 | } 110 | 111 | copied := make(http.Header, len(h)) 112 | for k, vv := range h { 113 | if vv == nil { 114 | copied[k] = nil 115 | continue 116 | } 117 | copied[k] = make([]string, len(vv)) 118 | copy(copied[k], vv) 119 | } 120 | 121 | return context.WithValue(ctx, RequestHeaderKey, copied), nil 122 | } 123 | 124 | func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { 125 | h, ok := ctx.Value(RequestHeaderKey).(http.Header) 126 | return h, ok 127 | } 128 | 129 | // SetHTTPResponseHeader sets an HTTP header key-value pair using a context 130 | // provided by a generated server, or a child of that context. 131 | // The server will include the header in its response for that request context. 132 | // 133 | // This can be used to respond with custom HTTP headers like "Cache-Control". 134 | // But note that HTTP headers are a implementation detail, 135 | // only visible by middleware, not by the clients or their responses. 136 | // 137 | // The header will be ignored (noop) if the context is invalid (i.e. using a new 138 | // context.Background() instead of passing the context from the handler). 139 | // 140 | // If called multiple times with the same key, it replaces any existing values 141 | // associated with that key. 142 | // 143 | // SetHTTPResponseHeader returns an error if the provided header key 144 | // would overwrite a header that is needed by xservice, like "Content-Type". 145 | func SetHTTPResponseHeader(ctx context.Context, key, value string) error { 146 | if key == "Content-Type" { 147 | return errors.New("header key can not be Content-Type") 148 | } 149 | 150 | responseWriter, ok := ctx.Value(ResponseWriterKey).(http.ResponseWriter) 151 | if ok { 152 | responseWriter.Header().Set(key, value) 153 | } // invalid context is ignored, not an error, this is to allow easy unit testing with mock servers 154 | 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /framework/xhttp/xhttp.go: -------------------------------------------------------------------------------- 1 | package xhttp 2 | 3 | const ContentTypeHeader string = "Content-Type" 4 | 5 | const ApplicationJson = "application/json" 6 | 7 | const ApplicationProtobuf = "application/protobuf" 8 | -------------------------------------------------------------------------------- /integration_tests/api_hello_world/api_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package helloworld_test 15 | 16 | import ( 17 | "context" 18 | "github.com/donutloop/xservice/integration_tests/api_hello_world" 19 | "net/http" 20 | "net/http/httptest" 21 | "os" 22 | "testing" 23 | ) 24 | 25 | type HelloWorldServer struct{} 26 | 27 | func (s *HelloWorldServer) Hello(ctx context.Context, req *helloworld.HelloReq) (*helloworld.HelloResp, error) { 28 | return &helloworld.HelloResp{Text: "Hello " + req.Subject}, nil 29 | } 30 | 31 | var JSONClient helloworld.HelloWorld 32 | var ProtobufferClient helloworld.HelloWorld 33 | 34 | func TestMain(m *testing.M) { 35 | handler := helloworld.NewHelloWorldServer(&HelloWorldServer{}, nil) 36 | mux := http.NewServeMux() 37 | mux.Handle(helloworld.HelloWorldPathPrefix+"Hello", handler) 38 | server := httptest.NewServer(mux) 39 | defer server.Close() 40 | 41 | JSONClient = helloworld.NewHelloWorldJSONClient(server.URL, &http.Client{}) 42 | ProtobufferClient = helloworld.NewHelloWorldProtobufferClient(server.URL, &http.Client{}) 43 | 44 | // call flag.Parse() here if TestMain uses flags 45 | os.Exit(m.Run()) 46 | } 47 | 48 | func TestHelloWorldJSONCall(t *testing.T) { 49 | resp, err := JSONClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"}) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | expectedMessage := "Hello World" 55 | if resp.Text != expectedMessage { 56 | t.Fatalf(`unexpected text (actual: "%s", expected: "%s")`, resp.Text, expectedMessage) 57 | } 58 | } 59 | 60 | func TestHelloWorldProtobufferCall(t *testing.T) { 61 | resp, err := ProtobufferClient.Hello(context.Background(), &helloworld.HelloReq{Subject: "World"}) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | expectedMessage := "Hello World" 67 | if resp.Text != expectedMessage { 68 | t.Fatalf(`unexpected text (actual: "%s", expected: "%s")`, resp.Text, expectedMessage) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /integration_tests/api_hello_world/gen.go: -------------------------------------------------------------------------------- 1 | package helloworld 2 | 3 | //go:generate protoc -I . ./helloworld.proto --xservice_out=. --go_out=. 4 | -------------------------------------------------------------------------------- /integration_tests/api_hello_world/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: helloworld.proto 3 | 4 | /* 5 | Package helloworld is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | helloworld.proto 9 | 10 | It has these top-level messages: 11 | HelloReq 12 | HelloResp 13 | */ 14 | package helloworld 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 30 | 31 | type HelloReq struct { 32 | Subject string `protobuf:"bytes,1,opt,name=subject" json:"subject,omitempty"` 33 | } 34 | 35 | func (m *HelloReq) Reset() { *m = HelloReq{} } 36 | func (m *HelloReq) String() string { return proto.CompactTextString(m) } 37 | func (*HelloReq) ProtoMessage() {} 38 | func (*HelloReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 39 | 40 | func (m *HelloReq) GetSubject() string { 41 | if m != nil { 42 | return m.Subject 43 | } 44 | return "" 45 | } 46 | 47 | type HelloResp struct { 48 | Text string `protobuf:"bytes,1,opt,name=text" json:"text,omitempty"` 49 | } 50 | 51 | func (m *HelloResp) Reset() { *m = HelloResp{} } 52 | func (m *HelloResp) String() string { return proto.CompactTextString(m) } 53 | func (*HelloResp) ProtoMessage() {} 54 | func (*HelloResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 55 | 56 | func (m *HelloResp) GetText() string { 57 | if m != nil { 58 | return m.Text 59 | } 60 | return "" 61 | } 62 | 63 | func init() { 64 | proto.RegisterType((*HelloReq)(nil), "example.helloworld.HelloReq") 65 | proto.RegisterType((*HelloResp)(nil), "example.helloworld.HelloResp") 66 | } 67 | 68 | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor0) } 69 | 70 | var fileDescriptor0 = []byte{ 71 | // 143 bytes of a gzipped FileDescriptorProto 72 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, 73 | 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xad, 74 | 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0xd5, 0x43, 0xc8, 0x28, 0xa9, 0x70, 0x71, 0x78, 0x80, 0x78, 0x41, 75 | 0xa9, 0x85, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa5, 0x49, 0x59, 0xa9, 0xc9, 0x25, 0x12, 0x8c, 0x0a, 76 | 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x92, 0x3c, 0x17, 0x27, 0x54, 0x55, 0x71, 0x81, 0x90, 0x10, 77 | 0x17, 0x4b, 0x49, 0x6a, 0x05, 0x4c, 0x0d, 0x98, 0x6d, 0x14, 0xc4, 0xc5, 0x05, 0x56, 0x10, 0x0e, 78 | 0x32, 0x54, 0xc8, 0x85, 0x8b, 0x15, 0xcc, 0x13, 0x92, 0xd1, 0xc3, 0xb4, 0x52, 0x0f, 0x66, 0x9f, 79 | 0x94, 0x2c, 0x1e, 0xd9, 0xe2, 0x02, 0x27, 0x9e, 0x28, 0x2e, 0x84, 0x78, 0x12, 0x1b, 0xd8, 0x0f, 80 | 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x65, 0x34, 0xd5, 0xb9, 0xd7, 0x00, 0x00, 0x00, 81 | } 82 | -------------------------------------------------------------------------------- /integration_tests/api_hello_world/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package example.helloworld; 3 | option go_package = "helloworld"; 4 | 5 | service HelloWorld { 6 | rpc Hello(HelloReq) returns (HelloResp); 7 | } 8 | 9 | message HelloReq { 10 | string subject = 1; 11 | } 12 | 13 | message HelloResp { 14 | string text = 1; 15 | } -------------------------------------------------------------------------------- /integration_tests/api_hello_world/helloworld.proto.go: -------------------------------------------------------------------------------- 1 | //Code generated by xproto [v0.1.0], DO NOT EDIT. 2 | //source: [helloworld.proto] 3 | //Package [helloworld] is a generated stub package. 4 | //This code was generated with github.com/donutloop/xservice [v0.1.0] 5 | //It is generated from these files: 6 | // [helloworld.proto] 7 | //package [helloworld] 8 | 9 | package helloworld 10 | 11 | import ( 12 | "context" 13 | "fmt" 14 | "log" 15 | "net/http" 16 | "strings" 17 | 18 | "github.com/donutloop/xservice/framework/errors" 19 | "github.com/donutloop/xservice/framework/hooks" 20 | "github.com/donutloop/xservice/framework/server" 21 | "github.com/donutloop/xservice/framework/transport" 22 | "github.com/donutloop/xservice/framework/xcontext" 23 | "github.com/donutloop/xservice/framework/xhttp" 24 | ) 25 | 26 | // //[HelloWorldPathPrefix HelloWorld] is used for all URL paths on a %!s(MISSING) server. 27 | //Requests are always: POST [HelloWorldPathPrefix] /method 28 | //It can be used in an HTTP mux to route requests 29 | const HelloWorldPathPrefix string = "/xservice/example.helloworld.HelloWorld/" 30 | 31 | // 143 bytes of a gzipped FileDescriptorProto 32 | var xserviceFileDescriptor0 = []byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xad, 0x48, 0xcc, 0x2d, 0xc8, 0x49, 0xd5, 0x43, 0xc8, 0x28, 0xa9, 0x70, 0x71, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0x42, 0x12, 0x5c, 0xec, 0xc5, 0xa5, 0x49, 0x59, 0xa9, 0xc9, 0x25, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x92, 0x3c, 0x17, 0x27, 0x54, 0x55, 0x71, 0x81, 0x90, 0x10, 0x17, 0x4b, 0x49, 0x6a, 0x05, 0x4c, 0x0d, 0x98, 0x6d, 0x14, 0xc4, 0xc5, 0x05, 0x56, 0x10, 0x0e, 0x32, 0x54, 0xc8, 0x85, 0x8b, 0x15, 0xcc, 0x13, 0x92, 0xd1, 0xc3, 0xb4, 0x52, 0x0f, 0x66, 0x9f, 0x94, 0x2c, 0x1e, 0xd9, 0xe2, 0x02, 0x27, 0x9e, 0x28, 0x2e, 0x84, 0x78, 0x12, 0x1b, 0xd8, 0x0f, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x65, 0x34, 0xd5, 0xb9, 0xd7, 0x00, 0x00, 0x00} 33 | 34 | type HelloWorld interface { 35 | Hello(ctx context.Context, req *HelloReq) (*HelloResp, error) 36 | } 37 | 38 | // helloWorldJSONClient wraps an http.client and sends JSON objects 39 | type helloWorldJSONClient struct { 40 | client transport.HTTPClient 41 | urls [1]string 42 | } 43 | 44 | // Hello sends an HelloReq JSON object to the server 45 | func (c *helloWorldJSONClient) Hello(ctx context.Context, in *HelloReq) (*HelloResp, error) { 46 | ctx = xcontext.WithPackageName(ctx, "example.helloworld") 47 | ctx = xcontext.WithServiceName(ctx, "HelloWorld") 48 | ctx = xcontext.WithMethodName(ctx, "Hello") 49 | out := new(HelloResp) 50 | err := transport.DoJSONRequest(ctx, c.client, c.urls[0], in, out) 51 | return out, err 52 | } 53 | 54 | // helloWorldProtobufferClient wraps an http.client and sends Protobuffer objects 55 | type helloWorldProtobufferClient struct { 56 | client transport.HTTPClient 57 | urls [1]string 58 | } 59 | 60 | // Hello sends an HelloReq Protobuffer object to the server 61 | func (c *helloWorldProtobufferClient) Hello(ctx context.Context, in *HelloReq) (*HelloResp, error) { 62 | ctx = xcontext.WithPackageName(ctx, "example.helloworld") 63 | ctx = xcontext.WithServiceName(ctx, "HelloWorld") 64 | ctx = xcontext.WithMethodName(ctx, "Hello") 65 | out := new(HelloResp) 66 | err := transport.DoProtobufferRequest(ctx, c.client, c.urls[0], in, out) 67 | return out, err 68 | } 69 | 70 | // helloWorldServer wraps an endpoint and implements http.Handler. 71 | type helloWorldServer struct { 72 | HelloWorld 73 | hooks *hooks.ServerHooks 74 | logErrorFunc transport.LogErrorFunc 75 | } 76 | 77 | func (s *helloWorldServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { 78 | transport.WriteErrorAndTriggerHooks(ctx, resp, err, s.hooks) 79 | } 80 | 81 | // ServeHTTP implements http.Handler. 82 | func (s *helloWorldServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 83 | ctx := req.Context() 84 | ctx = xcontext.WithPackageName(ctx, "example.helloworld") 85 | ctx = xcontext.WithServiceName(ctx, "HelloWorld") 86 | ctx = xcontext.WithResponseWriter(ctx, resp) 87 | var err error 88 | ctx, err = transport.CallRequestReceived(ctx, s.hooks) 89 | if err != nil { 90 | s.writeError(ctx, resp, err) 91 | return 92 | } 93 | if req.Method != http.MethodPost { 94 | msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) 95 | terr := errors.BadRouteError(msg, req.Method, req.URL.Path) 96 | s.writeError(ctx, resp, terr) 97 | return 98 | } 99 | 100 | switch req.URL.Path { 101 | case "/xservice/example.helloworld.HelloWorld/Hello": 102 | s.serveHello(ctx, resp, req) 103 | return 104 | 105 | default: 106 | msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 107 | terr := errors.BadRouteError(msg, req.Method, req.URL.Path) 108 | s.writeError(ctx, resp, terr) 109 | return 110 | } 111 | 112 | } 113 | 114 | // serveHello is used to set an decoder and encoder for a given content type 115 | func (s *helloWorldServer) serveHello(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 116 | header := req.Header.Get(xhttp.ContentTypeHeader) 117 | i := strings.Index(header, ";") 118 | if i == -1 { 119 | i = len(header) 120 | } 121 | modifiedHeader := strings.ToLower(header[:i]) 122 | modifiedHeader = strings.TrimSpace(modifiedHeader) 123 | if modifiedHeader == xhttp.ApplicationJson { 124 | s.serveHelloContent(ctx, resp, req, transport.DecodeJSONRequest, transport.EncodeJSONResponse) 125 | return 126 | } else if modifiedHeader == xhttp.ApplicationProtobuf { 127 | s.serveHelloContent(ctx, resp, req, transport.DecodePROTORequest, transport.EncodePROTOResponse) 128 | return 129 | } else { 130 | msg := fmt.Sprintf("unexpected Content-Type: %q", header) 131 | terr := errors.BadRouteError(msg, req.Method, req.URL.Path) 132 | s.writeError(ctx, resp, terr) 133 | } 134 | } 135 | 136 | // serveHelloContent sends object to requester 137 | func (s *helloWorldServer) serveHelloContent(ctx context.Context, resp http.ResponseWriter, req *http.Request, decodeRequest transport.DecodeRequestFunc, encodeResponse transport.EncodeResponseFunc) { 138 | var err error 139 | ctx = xcontext.WithMethodName(ctx, "Hello") 140 | ctx, err = transport.CallRequestRouted(ctx, s.hooks) 141 | if err != nil { 142 | s.writeError(ctx, resp, err) 143 | return 144 | } 145 | defer transport.Closebody(req.Body, s.logErrorFunc) 146 | 147 | reqContent := new(HelloReq) 148 | if err := decodeRequest(ctx, req, reqContent); err != nil { 149 | s.logErrorFunc("%v", err) 150 | s.writeError(ctx, resp, err) 151 | return 152 | } 153 | endpointWrapper := func() (*HelloResp, error) { 154 | deferWrapper := func() { 155 | if r := recover(); r != nil { 156 | terr := errors.InternalError("Internal service panic") 157 | s.writeError(ctx, resp, terr) 158 | panic(r) 159 | } 160 | } 161 | defer deferWrapper() 162 | 163 | return s.Hello(ctx, reqContent) 164 | 165 | } 166 | respContent, err := endpointWrapper() 167 | if err != nil { 168 | s.writeError(ctx, resp, err) 169 | return 170 | } 171 | if respContent == nil { 172 | terr := errors.InternalError("received a nil * HelloResp, and nil error while calling Hello. nil responses are not supported") 173 | s.logErrorFunc("%v", err) 174 | s.writeError(ctx, resp, terr) 175 | return 176 | } 177 | ctx = transport.CallResponsePrepared(ctx, s.hooks) 178 | if err := encodeResponse(ctx, resp, respContent); err != nil { 179 | s.logErrorFunc("%v", err) 180 | s.writeError(ctx, resp, err) 181 | return 182 | } 183 | transport.CallResponseSent(ctx, s.hooks) 184 | } 185 | 186 | // ServiceDescriptor describes an service. 187 | func (s *helloWorldServer) ServiceDescriptor() ([]uint8, int) { 188 | return xserviceFileDescriptor0, 0 189 | } 190 | 191 | // ProtocGenXServiceVersion returns which xservice version was used to generate that service 192 | func (s *helloWorldServer) ProtocGenXServiceVersion() string { 193 | return "v0.1.0" 194 | } 195 | 196 | // NewHelloWorldJSONClient constructs a new client, which wraps the http.client and implements HelloWorld 197 | func NewHelloWorldJSONClient(addr string, client transport.HTTPClient) HelloWorld { 198 | URLBase := transport.UrlBase(addr) 199 | prefix := URLBase + HelloWorldPathPrefix 200 | urls := [1]string{ 201 | prefix + "Hello", 202 | } 203 | httpClient, ok := client.(*http.Client) 204 | if ok == true { 205 | httpClient = transport.WithoutRedirects(httpClient) 206 | return &helloWorldJSONClient{ 207 | client: httpClient, 208 | urls: urls, 209 | } 210 | } 211 | return &helloWorldJSONClient{ 212 | client: client, 213 | urls: urls, 214 | } 215 | } 216 | 217 | // NewHelloWorldProtobufferClient constructs a new client, which wraps the http.client and implements HelloWorld 218 | func NewHelloWorldProtobufferClient(addr string, client transport.HTTPClient) HelloWorld { 219 | URLBase := transport.UrlBase(addr) 220 | prefix := URLBase + HelloWorldPathPrefix 221 | urls := [1]string{ 222 | prefix + "Hello", 223 | } 224 | httpClient, ok := client.(*http.Client) 225 | if ok == true { 226 | httpClient = transport.WithoutRedirects(httpClient) 227 | return &helloWorldProtobufferClient{ 228 | client: httpClient, 229 | urls: urls, 230 | } 231 | } 232 | return &helloWorldProtobufferClient{ 233 | client: client, 234 | urls: urls, 235 | } 236 | } 237 | 238 | // NewHelloWorldServer constructs a new server, and implements HelloWorld 239 | func NewHelloWorldServer(svc HelloWorld, hooks *hooks.ServerHooks, errorFunc ...transport.LogErrorFunc) server.Server { 240 | server := &helloWorldServer{ 241 | HelloWorld: svc, 242 | hooks: hooks, 243 | } 244 | if len(errorFunc) == 1 { 245 | server.logErrorFunc = errorFunc[0] 246 | } else { 247 | server.logErrorFunc = log.Printf 248 | } 249 | return server 250 | } 251 | -------------------------------------------------------------------------------- /internal/xgenerator/types/anonymous_func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | type AnonymousFuncGeneratorMetaData struct { 17 | Name string 18 | Lines []string 19 | Returns string 20 | Params string 21 | MethodOfTyp string 22 | TypShortcut string 23 | Fnc string 24 | Comment []string 25 | } 26 | 27 | type AnonymousFuncGenerator struct { 28 | GoGenerator 29 | GoBlockGenerator 30 | TypeFuncMetadata AnonymousFuncGeneratorMetaData 31 | } 32 | 33 | const AnonymousfuncTplName = "func" 34 | const AnonymousfuncTpl string = ` 35 | {{ .Name }} := func ({{ .Params }}) {{if .Returns }} ({{- .Returns }}) {{end}} { 36 | {{range $i, $line := .Lines}} 37 | {{- $line | safe }} 38 | {{- end -}} 39 | }` 40 | 41 | func NewAnonymousGoFunc(varName string, parameters []*Parameter, returns []TypeReference) (*AnonymousFuncGenerator, error) { 42 | 43 | gen := &AnonymousFuncGenerator{} 44 | 45 | if varName == "" { 46 | return nil, NewGeneratorErrorString(gen, "name of func is missing") 47 | } 48 | 49 | if err := ValidateIdent(varName); err != nil { 50 | return nil, NewGeneratorError(gen, err) 51 | } 52 | 53 | if err := ValidateParameters(parameters); err != nil { 54 | return nil, NewGeneratorError(gen, err) 55 | } 56 | 57 | gen.TypeFuncMetadata = AnonymousFuncGeneratorMetaData{ 58 | Name: Identifier(varName), 59 | Params: paramList(parameters), 60 | } 61 | 62 | if len(returns) > 0 { 63 | gen.TypeFuncMetadata.Returns = typeList(returns) 64 | } 65 | 66 | gen.TplName = AnonymousfuncTplName 67 | gen.InitTemplate(AnonymousfuncTpl) 68 | return gen, nil 69 | } 70 | 71 | func (gen *AnonymousFuncGenerator) Render() (string, error) { 72 | gen.TypeFuncMetadata.Lines = gen.GoBlockGenerator.MetaData.Lines 73 | s, err := gen.renderAndFormat(gen.TypeFuncMetadata) 74 | if err != nil { 75 | return s, NewGeneratorError(gen, err) 76 | } 77 | return s, err 78 | } 79 | -------------------------------------------------------------------------------- /internal/xgenerator/types/colsure.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import "fmt" 17 | 18 | type ClosureGeneratorMetaData struct { 19 | Name string 20 | Lines []string 21 | Returns string 22 | Params string 23 | MethodOfTyp string 24 | TypShortcut string 25 | Fnc string 26 | Comment []string 27 | } 28 | 29 | type ClosureGenerator struct { 30 | GoGenerator 31 | GoBlockGenerator 32 | TypeFuncMetadata ClosureGeneratorMetaData 33 | } 34 | 35 | const closureTplName = "closure" 36 | const closureTpl string = ` 37 | func {{ .Name }} ({{ .Params }}) {{if .Returns }} ({{- .Returns }}) {{end}} { 38 | {{if .Lines }} 39 | {{range $i, $line := .Lines}} 40 | {{- $line}} 41 | {{- end}} 42 | {{- end}} 43 | {{- .Fnc }} 44 | }` 45 | 46 | const innerFuncTplName string = "innerFunc" 47 | const innerFuncTpl string = ` 48 | return func ({{ .Params }}) {{if .Returns }} ({{- .Returns }}) {{end}} { 49 | {{range $i, $line := .Lines}} 50 | {{- $line | safe }} 51 | {{- end -}} 52 | }` 53 | 54 | func NewClosureFunc(name string, parameters []*Parameter, f *FuncGenerator) (*ClosureGenerator, error) { 55 | 56 | gen := &ClosureGenerator{} 57 | if name == "" { 58 | return nil, NewGeneratorErrorString(gen, "name of closure is missing") 59 | } 60 | 61 | if f == nil { 62 | return nil, NewGeneratorErrorString(gen, "inner func of closure is missing") 63 | } 64 | 65 | if err := ValidateIdent(name); err != nil { 66 | return nil, NewGeneratorError(gen, err) 67 | } 68 | 69 | if err := ValidateParameters(parameters); err != nil { 70 | return nil, NewGeneratorError(gen, err) 71 | } 72 | 73 | f.TplName = innerFuncTplName 74 | f.OverwriteTemplate(innerFuncTpl) 75 | innerFunc, err := f.Render() 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | gen.TypeFuncMetadata = ClosureGeneratorMetaData{ 81 | Name: Identifier(name), 82 | Params: paramList(parameters), 83 | Returns: fmt.Sprintf("%s func(%s) (%s)", f.TypeFuncMetadata.Name, f.TypeFuncMetadata.Params, f.TypeFuncMetadata.Returns), 84 | Fnc: innerFunc, 85 | } 86 | 87 | gen.TplName = closureTplName 88 | gen.InitTemplate(closureTpl) 89 | return gen, nil 90 | } 91 | 92 | func (gen *ClosureGenerator) Render() (string, error) { 93 | gen.TypeFuncMetadata.Lines = gen.GoBlockGenerator.MetaData.Lines 94 | s, err := gen.renderAndFormat(gen.TypeFuncMetadata) 95 | if err != nil { 96 | return s, NewGeneratorError(gen, err) 97 | } 98 | return s, err 99 | } 100 | -------------------------------------------------------------------------------- /internal/xgenerator/types/comment.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import "fmt" 17 | 18 | // Template of comment 19 | const commentTpl string = ` 20 | {{if .Comment }} 21 | {{range $i, $line := .Comment}} 22 | // {{- $line -}} 23 | {{end}} 24 | {{end}} 25 | ` 26 | const commentTplName string = "const" 27 | 28 | type CommentMetaData struct { 29 | Comment []string 30 | } 31 | 32 | type CommentGenerator struct { 33 | GoGenerator 34 | CommentMetaData CommentMetaData 35 | } 36 | 37 | func NewGoComment() *CommentGenerator { 38 | 39 | commentGen := new(CommentGenerator) 40 | commentGen.TplName = commentTplName 41 | commentGen.InitTemplate(commentTpl) 42 | 43 | return commentGen 44 | } 45 | 46 | func (gen *CommentGenerator) P(s string) { 47 | gen.CommentMetaData.Comment = append(gen.CommentMetaData.Comment, s) 48 | } 49 | 50 | func (gen *CommentGenerator) Pf(format string, a ...interface{}) { 51 | gen.CommentMetaData.Comment = append(gen.CommentMetaData.Comment, fmt.Sprintf(format, a)) 52 | } 53 | 54 | func (gen *CommentGenerator) Render() (string, error) { 55 | s, err := gen.renderAndFormat(gen.CommentMetaData) 56 | if err != nil { 57 | return s, NewGeneratorError(gen, err) 58 | } 59 | return s, err 60 | } 61 | -------------------------------------------------------------------------------- /internal/xgenerator/types/const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | // Template of const 17 | const constTpl string = ` 18 | {{ if .Comment }} // {{ .Comment }} {{ end }} 19 | const {{ .Name }} {{ .Typ }} = {{ .Value }}` 20 | const constTplName string = "const" 21 | 22 | type ConstMetadata struct { 23 | Name string 24 | Value string 25 | Typ string 26 | Comment string 27 | } 28 | 29 | type ConstGenerator struct { 30 | GoGenerator 31 | ConstMetaData ConstMetadata 32 | } 33 | 34 | func NewGoConst(name string, typeReference TypeReference, value string, commentGenerator *CommentGenerator) (*ConstGenerator, error) { 35 | 36 | constGen := ConstGenerator{} 37 | 38 | if name == "" { 39 | return nil, NewGeneratorErrorString(constGen, "name of const is empty") 40 | } 41 | 42 | if err := ValidateIdent(name); err != nil { 43 | return nil, NewGeneratorError(constGen, err) 44 | } 45 | 46 | if value == "" { 47 | return nil, NewGeneratorErrorString(constGen, "value of const is missing") 48 | } 49 | 50 | if typeReference == nil { 51 | return nil, NewGeneratorErrorString(constGen, "TypeReference is missing") 52 | 53 | } 54 | 55 | if typeReference.GetName() == "" { 56 | return nil, NewGeneratorErrorString(constGen, "type is missing") 57 | } 58 | 59 | constGen.ConstMetaData = ConstMetadata{ 60 | Name: name, 61 | Value: value, 62 | Typ: typeReference.GetName(), 63 | } 64 | 65 | if commentGenerator != nil { 66 | comment, err := commentGenerator.Render() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | if comment != "" { 72 | constGen.ConstMetaData.Comment = comment 73 | } 74 | } 75 | 76 | constGen.TplName = constTplName 77 | constGen.InitTemplate(constTpl) 78 | 79 | return &constGen, nil 80 | } 81 | 82 | func (gen *ConstGenerator) Render() (string, error) { 83 | s, err := gen.renderAndFormat(gen.ConstMetaData) 84 | if err != nil { 85 | return s, NewGeneratorError(gen, err) 86 | } 87 | return s, err 88 | } 89 | -------------------------------------------------------------------------------- /internal/xgenerator/types/const_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types_test 15 | 16 | import ( 17 | "github.com/donutloop/xservice/internal/xgenerator/types" 18 | "testing" 19 | ) 20 | 21 | func TestNewGoConstAndRender(t *testing.T) { 22 | 23 | constGenerator, err := types.NewGoConst("dummy", types.String, `"value"`, nil) 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | 29 | renderedConst, err := constGenerator.Render() 30 | if err != nil { 31 | t.Error(err) 32 | return 33 | } 34 | 35 | expectedConst := `const dummy string = "value"` 36 | if expectedConst != renderedConst { 37 | t.Errorf(`Unexpected const definition (Actual: "%s", Expected: "%s")`, renderedConst, expectedConst) 38 | return 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/xgenerator/types/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "fmt" 18 | "golang.org/x/tools/imports" 19 | "os" 20 | "path/filepath" 21 | "strings" 22 | ) 23 | 24 | const packageTplName string = "package" 25 | const packageTpl string = ` 26 | 27 | {{if .HeaderComment }} 28 | {{ .HeaderComment }} 29 | {{end}} 30 | 31 | package {{ .Pkg }} 32 | 33 | import ( 34 | {{range $i, $ip := .Imports}} 35 | {{if $ip.Alias }} {{$ip.Alias}} {{end}} "{{$ip.ImportPath}}" 36 | {{- end}} 37 | ) 38 | 39 | {{range $i, $const := .Consts}} 40 | {{- $const}} 41 | {{end}} 42 | 43 | {{range $i, $var := .Vars}} 44 | {{- $var}} 45 | {{end}} 46 | 47 | {{range $i, $Interface := .Interfaces}} 48 | {{- $Interface}} 49 | {{end}} 50 | 51 | {{range $i, $Prototype := .Prototypes}} 52 | {{- $Prototype}} 53 | {{end}} 54 | 55 | {{if .Types }} 56 | type({{range $i, $typ := .Types}} 57 | {{- $typ}} 58 | {{end}}) 59 | {{end}} 60 | 61 | {{if .TypesWithoutGroup }} 62 | {{range $i, $typ := .TypesWithoutGroup}} 63 | {{- $typ}} 64 | {{end}} 65 | {{end}} 66 | 67 | {{range $i, $TypeWithMethods := .TypesWithMethods}} 68 | {{ $TypeWithMethods}} 69 | {{end}} 70 | 71 | {{range $i, $func := .Funcs}} 72 | {{- $func }} 73 | {{end}} 74 | ` 75 | 76 | type Type interface { 77 | Render() (string, error) 78 | GetMethods() []*MethodGenerator 79 | } 80 | 81 | type Generator interface { 82 | Render() (string, error) 83 | } 84 | 85 | type ImportDecl struct { 86 | Alias string 87 | ImportPath string 88 | } 89 | 90 | type fileMetadata struct { 91 | Imports []ImportDecl 92 | FileName string 93 | DirToScan string 94 | SourceFile string 95 | Pkg string 96 | Types []string 97 | Funcs []string 98 | Consts []string 99 | Vars []string 100 | Prototypes []string 101 | Interfaces []string 102 | TypesWithMethods []string 103 | TypesWithoutGroup []string 104 | HeaderComment string 105 | } 106 | 107 | type FileGenerator struct { 108 | GoGenerator 109 | FileMetaData fileMetadata 110 | } 111 | 112 | func NewGoFile(pkg string, fileName string) (*FileGenerator, error) { 113 | 114 | gen := FileGenerator{} 115 | if pkg == "" { 116 | return nil, NewGeneratorErrorString(gen, "pkg of go file is missing") 117 | } 118 | 119 | if err := ValidateIdent(pkg); err != nil { 120 | return nil, NewGeneratorError(gen, err) 121 | } 122 | 123 | if fileName == "" { 124 | return nil, NewGeneratorErrorString(gen, "filename of go file is missing") 125 | } 126 | 127 | gen.FileMetaData = fileMetadata{ 128 | Pkg: pkg, 129 | } 130 | 131 | gen.FileMetaData.FileName = gen.prepareFileName(fileName) 132 | 133 | gen.TplName = packageTplName 134 | gen.InitTemplate(packageTpl) 135 | return &gen, nil 136 | } 137 | 138 | func (gen *FileGenerator) Type(typs ...*StructGenerator) error { 139 | 140 | rendered, err := gen.GoGenerator.renderAll(typs) 141 | if err != nil { 142 | return err 143 | } 144 | gen.FileMetaData.Types = append(gen.FileMetaData.Types, rendered...) 145 | 146 | return nil 147 | } 148 | 149 | func (gen *FileGenerator) HeaderComment(generator *CommentGenerator) error { 150 | 151 | comment, err := generator.Render() 152 | if err != nil { 153 | return NewGeneratorError(gen, err) 154 | } 155 | 156 | gen.FileMetaData.HeaderComment = comment 157 | return nil 158 | } 159 | 160 | func (gen *FileGenerator) Import(alias string, Import string) error { 161 | if Import == "" { 162 | return NewGeneratorErrorString(gen, "import is a empty string") 163 | } 164 | 165 | i := ImportDecl{ 166 | ImportPath: Import, 167 | Alias: alias, 168 | } 169 | 170 | gen.FileMetaData.Imports = append(gen.FileMetaData.Imports, i) 171 | return nil 172 | } 173 | 174 | func (gen *FileGenerator) TypesWithoutGroup(typs ...*StructGenerator) error { 175 | 176 | rendered, err := gen.renderAll(typs) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | gen.FileMetaData.TypesWithoutGroup = append(gen.FileMetaData.TypesWithoutGroup, rendered...) 182 | 183 | return nil 184 | } 185 | 186 | func (gen *FileGenerator) Func(fncs ...*FuncGenerator) error { 187 | 188 | rendered, err := gen.renderAll(fncs) 189 | if err != nil { 190 | return err 191 | } 192 | 193 | gen.FileMetaData.Funcs = append(gen.FileMetaData.Funcs, rendered...) 194 | return nil 195 | } 196 | 197 | func (gen *FileGenerator) Closure(colsures ...*ClosureGenerator) error { 198 | 199 | rendered, err := gen.renderAll(colsures) 200 | if err != nil { 201 | return err 202 | } 203 | 204 | gen.FileMetaData.Funcs = append(gen.FileMetaData.Funcs, rendered...) 205 | return nil 206 | } 207 | 208 | func (gen *FileGenerator) Const(g *ConstGenerator) error { 209 | 210 | if g == nil { 211 | return NewGeneratorErrorString(gen, "generator is nil") 212 | } 213 | 214 | cnst, err := g.Render() 215 | if err != nil { 216 | return NewGeneratorError(gen, err) 217 | } 218 | 219 | gen.FileMetaData.Consts = append(gen.FileMetaData.Consts, cnst) 220 | return nil 221 | } 222 | 223 | func (gen *FileGenerator) Var(varObject string) error { 224 | gen.FileMetaData.Vars = append(gen.FileMetaData.Vars, varObject) 225 | return nil 226 | } 227 | 228 | func (gen *FileGenerator) Prototype(g *PrototypeGenerator) error { 229 | 230 | if g == nil { 231 | return NewGeneratorErrorString(gen, "generator is nil") 232 | } 233 | 234 | prototype, err := g.Render() 235 | if err != nil { 236 | return NewGeneratorError(gen, err) 237 | } 238 | 239 | gen.FileMetaData.Prototypes = append(gen.FileMetaData.Prototypes, prototype) 240 | return nil 241 | } 242 | 243 | func (gen *FileGenerator) Interface(g *InterfaceGenerator) error { 244 | 245 | if g == nil { 246 | return NewGeneratorErrorString(gen, "generator is nil") 247 | } 248 | 249 | i, err := g.Render() 250 | if err != nil { 251 | return NewGeneratorError(gen, err) 252 | } 253 | 254 | gen.FileMetaData.Interfaces = append(gen.FileMetaData.Interfaces, i) 255 | return nil 256 | } 257 | 258 | func (gen *FileGenerator) TypesWithMethods(tg Type) error { 259 | 260 | t, err := tg.Render() 261 | if err != nil { 262 | return NewGeneratorError(gen, err) 263 | } 264 | 265 | methods := make([]string, 0, len(tg.GetMethods())) 266 | for _, method := range tg.GetMethods() { 267 | m, err := method.Render() 268 | if err != nil { 269 | return NewGeneratorError(gen, err) 270 | } 271 | methods = append(methods, m) 272 | } 273 | 274 | gen.FileMetaData.TypesWithMethods = append(gen.FileMetaData.TypesWithMethods, t) 275 | gen.FileMetaData.TypesWithMethods = append(gen.FileMetaData.TypesWithMethods, methods...) 276 | return nil 277 | } 278 | 279 | func (gen *FileGenerator) Render() (string, error) { 280 | s, err := gen.render(gen.FileMetaData) 281 | if err != nil { 282 | return s, NewGeneratorError(gen, err) 283 | } 284 | return s, nil 285 | } 286 | 287 | func (gen *FileGenerator) RenderBytes() ([]byte, error) { 288 | b, err := gen.renderBytes(gen.FileMetaData) 289 | if err != nil { 290 | return b, NewGeneratorError(gen, err) 291 | } 292 | return b, err 293 | } 294 | 295 | func (gen *FileGenerator) prepareFileName(fileName string) string { 296 | filePathParts := strings.Split(fileName, string(os.PathSeparator)) 297 | if len(filePathParts) > 1 { 298 | fileName = GoFileName(filePathParts[len(filePathParts)-1]) 299 | filePathParts[len(filePathParts)-1] = fileName 300 | return string(os.PathSeparator) + filepath.Join(filePathParts...) 301 | } 302 | fileName = GoFileName(fileName) 303 | return fileName 304 | } 305 | 306 | func (gen *FileGenerator) RenderAndFormatCode() ([]byte, error) { 307 | 308 | pkgContent, err := gen.RenderBytes() 309 | if err != nil { 310 | return nil, NewGeneratorError(gen, err) 311 | } 312 | 313 | formatedContent, err := imports.Process(gen.GetFileName(), pkgContent, nil) 314 | if err != nil { 315 | return nil, NewGeneratorErrorString(gen, fmt.Sprintf( 316 | `While the formting the source code is a error occurd (%v) 317 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 318 | |||||||||||||||||||||||||||||||||||||||Source code|||||||||||||||||||||||||||||||||||||||||||| 319 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 320 | %s 321 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 322 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 323 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||`, 324 | err, 325 | pkgContent, 326 | )) 327 | } 328 | 329 | return formatedContent, err 330 | } 331 | 332 | func (gen *FileGenerator) GetFileName() string { 333 | return gen.FileMetaData.FileName 334 | } 335 | -------------------------------------------------------------------------------- /internal/xgenerator/types/func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | type FuncGeneratorMetaData struct { 17 | Name string 18 | Lines []string 19 | Returns string 20 | Params string 21 | MethodOfTyp string 22 | TypShortcut string 23 | Fnc string 24 | Comment string 25 | } 26 | 27 | type FuncGenerator struct { 28 | GoGenerator 29 | GoBlockGenerator 30 | TypeFuncMetadata FuncGeneratorMetaData 31 | } 32 | 33 | const funcTplName = "func" 34 | const funcTpl string = ` 35 | {{ if .Comment }} // {{ .Comment }} {{ end }} 36 | func {{ .Name }} ({{ .Params }}) {{if .Returns }} ({{- .Returns }}) {{end}} { 37 | {{range $i, $line := .Lines}} 38 | {{- $line | safe }} 39 | {{- end -}} 40 | }` 41 | 42 | func NewGoFunc(name string, parameters []*Parameter, returns []TypeReference, comment string) (*FuncGenerator, error) { 43 | 44 | gen := &FuncGenerator{} 45 | 46 | if name == "" { 47 | return nil, NewGeneratorErrorString(gen, "name of func is missing") 48 | } 49 | 50 | if err := ValidateIdent(name); err != nil { 51 | return nil, NewGeneratorError(gen, err) 52 | } 53 | 54 | if err := ValidateParameters(parameters); err != nil { 55 | return nil, NewGeneratorError(gen, err) 56 | } 57 | 58 | gen.TypeFuncMetadata = FuncGeneratorMetaData{ 59 | Name: Identifier(name), 60 | Params: paramList(parameters), 61 | Comment: comment, 62 | } 63 | 64 | if len(returns) > 0 { 65 | gen.TypeFuncMetadata.Returns = typeList(returns) 66 | } 67 | 68 | gen.TplName = funcTplName 69 | gen.InitTemplate(funcTpl) 70 | return gen, nil 71 | } 72 | 73 | func (gen *FuncGenerator) Render() (string, error) { 74 | gen.TypeFuncMetadata.Lines = gen.GoBlockGenerator.MetaData.Lines 75 | s, err := gen.renderAndFormat(gen.TypeFuncMetadata) 76 | if err != nil { 77 | return s, NewGeneratorError(gen, err) 78 | } 79 | return s, err 80 | } 81 | -------------------------------------------------------------------------------- /internal/xgenerator/types/func_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types_test 15 | 16 | import ( 17 | "github.com/donutloop/xservice/internal/xgenerator/types" 18 | "testing" 19 | ) 20 | 21 | func TestNewFuncAndRender(t *testing.T) { 22 | funcGenerator, err := types.NewGoFunc("Split", 23 | []*types.Parameter{ 24 | types.NewParameterWithTypeReference("s", types.String), 25 | types.NewParameterWithTypeReference("sep", types.String), 26 | }, 27 | []types.TypeReference{ 28 | types.String, 29 | types.String, 30 | }, 31 | "", 32 | ) 33 | if err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | 38 | renderedFunc, err := funcGenerator.Render() 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | 44 | expectedFunc := `func Split(s string, sep string) (string, string) { 45 | }` 46 | if expectedFunc != renderedFunc { 47 | t.Errorf(`Unexpected func definition (Actual: "%s", Expected: "%s")`, renderedFunc, expectedFunc) 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/xgenerator/types/genutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | // This file contains some code from https://github.com/twitchtv/twirp/: 17 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. All rights reserved. 18 | // https://github.com/twitchtv/twirp/ 19 | 20 | import ( 21 | "bytes" 22 | "errors" 23 | "fmt" 24 | "go/token" 25 | "net/http" 26 | "reflect" 27 | "regexp" 28 | "strings" 29 | "unicode" 30 | "unicode/utf8" 31 | ) 32 | 33 | // BaseName the last path element of a slash-delimited name, with the last 34 | // dotted suffix removed. 35 | func BaseName(name string) string { 36 | // First, find the last element 37 | if i := strings.LastIndex(name, "/"); i >= 0 { 38 | name = name[i+1:] 39 | } 40 | // Now drop the suffix 41 | if i := strings.LastIndex(name, "."); i >= 0 { 42 | name = name[0:i] 43 | } 44 | return name 45 | } 46 | 47 | // SnakeCase converts a string from CamelCase to snake_case. 48 | func SnakeCase(s string) string { 49 | var buf bytes.Buffer 50 | for i, r := range s { 51 | if unicode.IsUpper(r) && i > 0 { 52 | fmt.Fprintf(&buf, "_") 53 | } 54 | r = unicode.ToLower(r) 55 | fmt.Fprintf(&buf, "%c", r) 56 | } 57 | return buf.String() 58 | } 59 | 60 | // Is c an ASCII lower-case letter? 61 | func isASCIILower(c byte) bool { 62 | return 'a' <= c && c <= 'z' 63 | } 64 | 65 | // Is c an ASCII digit? 66 | func isASCIIDigit(c byte) bool { 67 | return '0' <= c && c <= '9' 68 | } 69 | 70 | func CamelCase(s string) string { 71 | if s == "" { 72 | return "" 73 | } 74 | t := make([]byte, 0, 32) 75 | i := 0 76 | if s[0] == '_' { 77 | // Need a capital letter; drop the '_'. 78 | t = append(t, 'X') 79 | i++ 80 | } 81 | // Invariant: if the next letter is lower case, it must be converted 82 | // to upper case. 83 | // 84 | // That is, we process a word at a time, where words are marked by _ or upper 85 | // case letter. Digits are treated as words. 86 | for ; i < len(s); i++ { 87 | c := s[i] 88 | if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) { 89 | continue // Skip the underscore in s. 90 | } 91 | if isASCIIDigit(c) { 92 | t = append(t, c) 93 | continue 94 | } 95 | // Assume we have a letter now - if not, it's a bogus identifier. The next 96 | // word is a sequence of characters that must start upper case. 97 | if isASCIILower(c) { 98 | c ^= ' ' // Make it a capital letter. 99 | } 100 | t = append(t, c) // Guaranteed not lower case. 101 | // Accept lower case sequence that follows. 102 | for i+1 < len(s) && isASCIILower(s[i+1]) { 103 | i++ 104 | t = append(t, s[i]) 105 | } 106 | } 107 | return string(t) 108 | } 109 | 110 | var noneLiteralChars = regexp.MustCompile(`([^\w(),".&]{0,})`) 111 | 112 | func identifierList(names []string) string { 113 | var text bytes.Buffer 114 | for _, name := range names { 115 | if text.Len() > 0 { 116 | text.WriteString(", ") 117 | } 118 | text.WriteString(Identifier(name)) 119 | } 120 | return text.String() 121 | } 122 | 123 | func ValueList(names []string) string { 124 | return strings.Join(names, ", ") 125 | } 126 | 127 | func typeList(types []TypeReference) string { 128 | 129 | var text bytes.Buffer 130 | for _, typ := range types { 131 | if text.Len() > 0 { 132 | text.WriteString(", ") 133 | } 134 | text.WriteString(typ.GetName()) 135 | } 136 | 137 | return text.String() 138 | } 139 | 140 | func paramList(parameters []*Parameter) string { 141 | 142 | var text bytes.Buffer 143 | for _, parameter := range parameters { 144 | if text.Len() > 0 { 145 | text.WriteString(", ") 146 | } 147 | 148 | text.WriteString(Identifier(parameter.NameOfParameter)) 149 | text.WriteString(" ") 150 | text.WriteString(parameter.Typ.GetName()) 151 | } 152 | 153 | return text.String() 154 | } 155 | 156 | func ExportedIdentifier(name string) string { 157 | return Identifier(strings.Title(name)) 158 | } 159 | 160 | func UnexportedIdentifier(name string) string { 161 | return Identifier(UnTitle(name)) 162 | } 163 | 164 | func Identifier(name string) string { 165 | variadic := strings.HasSuffix(name, "...") 166 | ident := noneLiteralChars.ReplaceAllString(name, "") 167 | if variadic { 168 | ident += "..." 169 | } 170 | return ident 171 | } 172 | 173 | func UnTitle(s string) string { 174 | 175 | if len(s) == 0 { 176 | return s 177 | } 178 | 179 | r, width := utf8.DecodeRuneInString(s) 180 | return fmt.Sprintf("%c", unicode.ToLower(r)) + s[width:] 181 | } 182 | 183 | func GoFileName(fileName string) string { 184 | return fmt.Sprintf("%s.go", SnakeCase(fileName)) 185 | } 186 | 187 | var statusCode = map[int]string{ 188 | http.StatusContinue: "http.StatusContinue", 189 | http.StatusSwitchingProtocols: "http.StatusSwitchingProtocols", 190 | http.StatusProcessing: "http.StatusProcessing", 191 | 192 | http.StatusOK: "http.StatusOK", 193 | http.StatusCreated: "http.StatusCreated", 194 | http.StatusAccepted: "http.StatusAccepted", 195 | http.StatusNonAuthoritativeInfo: "http.StatusNonAuthoritativeInfo", 196 | http.StatusNoContent: "http.StatusNoContent", 197 | http.StatusResetContent: "http.StatusResetContent", 198 | http.StatusPartialContent: "http.StatusPartialContent", 199 | http.StatusMultiStatus: "http.StatusMultiStatus", 200 | http.StatusAlreadyReported: "http.StatusAlreadyReported", 201 | http.StatusIMUsed: "http.StatusIMUsed", 202 | 203 | http.StatusMultipleChoices: "http.StatusMultipleChoices", 204 | http.StatusMovedPermanently: "http.StatusMovedPermanently", 205 | http.StatusFound: "http.StatusFound", 206 | http.StatusSeeOther: "http.StatusSeeOther", 207 | http.StatusNotModified: "http.StatusNotModified", 208 | http.StatusUseProxy: "http.StatusUseProxy", 209 | http.StatusTemporaryRedirect: "http.StatusTemporaryRedirect", 210 | http.StatusPermanentRedirect: "http.StatusPermanentRedirect", 211 | 212 | http.StatusBadRequest: "http.StatusBadRequest", 213 | http.StatusUnauthorized: "http.StatusUnauthorized", 214 | http.StatusPaymentRequired: "http.StatusPaymentRequired", 215 | http.StatusForbidden: "http.StatusForbidden", 216 | http.StatusNotFound: "http.StatusNotFound", 217 | http.StatusMethodNotAllowed: "http.StatusMethodNotAllowed", 218 | http.StatusNotAcceptable: "http.StatusNotAcceptable", 219 | http.StatusProxyAuthRequired: "http.StatusProxyAuthRequired", 220 | http.StatusRequestTimeout: "http.StatusRequestTimeout", 221 | http.StatusConflict: "http.StatusConflict", 222 | http.StatusGone: "http.StatusGone", 223 | http.StatusLengthRequired: "http.StatusLengthRequired:", 224 | http.StatusPreconditionFailed: "http.StatusPreconditionFailed", 225 | http.StatusRequestEntityTooLarge: "http.StatusRequestEntityTooLarge", 226 | http.StatusRequestURITooLong: "http.StatusRequestURITooLong", 227 | http.StatusUnsupportedMediaType: "http.StatusUnsupportedMediaType", 228 | http.StatusRequestedRangeNotSatisfiable: "http.StatusRequestedRangeNotSatisfiable", 229 | http.StatusExpectationFailed: "http.StatusExpectationFailed", 230 | http.StatusTeapot: "http.StatusTeapot", 231 | http.StatusUnprocessableEntity: "http.StatusUnprocessableEntity", 232 | http.StatusLocked: "http.StatusLocked", 233 | http.StatusFailedDependency: "http.StatusFailedDependency", 234 | http.StatusUpgradeRequired: "http.StatusUpgradeRequired", 235 | http.StatusPreconditionRequired: "http.StatusPreconditionRequired", 236 | http.StatusTooManyRequests: "http.StatusTooManyRequests", 237 | http.StatusRequestHeaderFieldsTooLarge: "http.StatusRequestHeaderFieldsTooLarge", 238 | http.StatusUnavailableForLegalReasons: "http.StatusUnavailableForLegalReasons", 239 | 240 | http.StatusInternalServerError: "http.StatusInternalServerError", 241 | http.StatusNotImplemented: "http.StatusNotImplemented", 242 | http.StatusBadGateway: "http.StatusBadGateway", 243 | http.StatusServiceUnavailable: "http.StatusServiceUnavailable", 244 | http.StatusGatewayTimeout: "http.StatusGatewayTimeout", 245 | http.StatusHTTPVersionNotSupported: "http.StatusHTTPVersionNotSupported", 246 | http.StatusVariantAlsoNegotiates: "http.StatusVariantAlsoNegotiates", 247 | http.StatusInsufficientStorage: "http.StatusInsufficientStorage", 248 | http.StatusLoopDetected: "http.StatusLoopDetected", 249 | http.StatusNotExtended: "http.StatusNotExtended", 250 | http.StatusNetworkAuthenticationRequired: "http.StatusNetworkAuthenticationRequired", 251 | } 252 | 253 | // StatusCodeVar returns a var for the HTTP status code. It returns the empty 254 | // string if the code is unknown. 255 | func StatusCodeVar(code int) string { 256 | return statusCode[code] 257 | } 258 | 259 | func StatusText(statusCode int) string { 260 | status := StatusCodeVar(statusCode) 261 | if status == "" { 262 | return "" 263 | } 264 | return strings.Replace(status, "http.", "", 1) 265 | } 266 | 267 | func ValidateIdent(ident string) error { 268 | if t := token.Lookup(ident); t != token.IDENT { 269 | return fmt.Errorf("identifier is a key word (%s)", ident) 270 | } 271 | return nil 272 | } 273 | 274 | func ValidateIdents(idents []string) error { 275 | for _, ident := range idents { 276 | if t := token.Lookup(ident); t != token.IDENT { 277 | return fmt.Errorf("identifier is a key word (%s)", ident) 278 | } 279 | } 280 | return nil 281 | } 282 | 283 | func ValidateParameters(parameters []*Parameter) error { 284 | for _, parameter := range parameters { 285 | 286 | if parameter == nil { 287 | return errors.New("TypeReference is missing") 288 | } 289 | 290 | if parameter.NameOfParameter == "" { 291 | return errors.New("name of parameter is missing") 292 | } 293 | 294 | if parameter.Typ.GetName() == "" { 295 | return errors.New("value of parameter is missing") 296 | } 297 | 298 | if err := ValidateIdent(parameter.NameOfParameter); err != nil { 299 | return err 300 | } 301 | } 302 | 303 | return nil 304 | } 305 | 306 | func ValidateOperation(op token.Token) error { 307 | if op.Precedence() == token.LowestPrec { 308 | return fmt.Errorf("operation is invalid in operation scope (%s)", op.String()) 309 | } 310 | return nil 311 | } 312 | 313 | func ValidateIfOperation(op token.Token) error { 314 | 315 | switch op.Precedence() { 316 | case 1: 317 | fallthrough 318 | case 2: 319 | fallthrough 320 | case 3: 321 | return nil 322 | } 323 | 324 | return fmt.Errorf("operation is invalid in if scope (%s)", op.String()) 325 | } 326 | 327 | func NewGeneratorErrorString(generator interface{}, s string) *GeneratorErrorString { 328 | return &GeneratorErrorString{ 329 | s: s, 330 | generator: generator, 331 | } 332 | } 333 | 334 | type GeneratorErrorString struct { 335 | s string 336 | generator interface{} 337 | } 338 | 339 | func (e *GeneratorErrorString) Error() string { 340 | return fmt.Sprintf("%s: %s", reflect.TypeOf(e.generator).String(), e.s) 341 | } 342 | 343 | func NewGeneratorError(generator interface{}, err error) *GeneratorError { 344 | return &GeneratorError{ 345 | err: err, 346 | generator: generator, 347 | } 348 | } 349 | 350 | type GeneratorError struct { 351 | err error 352 | generator interface{} 353 | } 354 | 355 | func (e *GeneratorError) Error() string { 356 | return fmt.Sprintf("%s: %s", reflect.TypeOf(e.generator).String(), e.err) 357 | } 358 | -------------------------------------------------------------------------------- /internal/xgenerator/types/genutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import "testing" 17 | 18 | func TestUnsafeIdentifierList(t *testing.T) { 19 | tests := []struct { 20 | name string 21 | input []string 22 | output string 23 | }{ 24 | { 25 | name: "3 identifier", 26 | input: []string{"id1", "id2", "id3"}, 27 | output: "id1, id2, id3", 28 | }, 29 | } 30 | for _, test := range tests { 31 | t.Run(test.name, func(t *testing.T) { 32 | s := ValueList(test.input) 33 | if s != test.output { 34 | t.Errorf(`unepxected identifier list (actual: %s, expected: %s)`, s, test.output) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/xgenerator/types/go.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "fmt" 20 | "go/format" 21 | "html" 22 | "reflect" 23 | "strings" 24 | "text/template" 25 | ) 26 | 27 | const ( 28 | CallerTpl string = "%s(%s)\n" 29 | DefAssginCallTpl string = "%s := %s(%s)\n" 30 | DefCallTpl string = "%s = %s(%s)\n" 31 | DefAppendTpl string = "%s = append(%s)\n" 32 | DefSCallTpl string = "%s := %s(%s)" 33 | DefVarShortTpl string = "%s := %s\n" 34 | DefVarLongTpl string = "var %s %s\n" 35 | DefNewTpl string = "%s := new(%s)\n" 36 | DefOperationTpl string = "%s := %s %s %s\n" 37 | DefAssertTpl string = "%s := %s.(%s)\n" 38 | DefStructTpl string = "%s := %s.%s\n" 39 | DefReturnTpl string = "return \n" 40 | DefReturnWithValuesTpl string = "return %s\n" 41 | CommandTpl string = "%s %s\n" 42 | StructAssignTpl string = "%s.%s = %s\n" 43 | IfStatmentTpl string = "if %s %s %s {\n" 44 | IfStatmentWithOwnScopeTpl string = "if %s; %s %s %s {\n" 45 | ElseStatmentTpl string = "} else {\n" 46 | ElseIfStatmentTpl string = "} else if %s %s %s {\n" 47 | IfEndTpl string = "}\n" 48 | RangeTpl string = "for %s,%s := range %s {\n" 49 | RangeEndTpl string = "}\n" 50 | DeferTpl string = "defer %s \n" 51 | ) 52 | 53 | type GoGenerator struct { 54 | tpl *template.Template 55 | TplName string 56 | } 57 | 58 | func (gen *GoGenerator) InitTemplate(tpl string) { 59 | funcMap := template.FuncMap{ 60 | "safe": html.UnescapeString, 61 | } 62 | gen.tpl = template.Must(template.New(gen.TplName).Funcs(funcMap).Parse(tpl)) 63 | } 64 | 65 | func (gen *GoGenerator) OverwriteTemplate(tpl string) { 66 | gen.InitTemplate(tpl) 67 | } 68 | 69 | func (gen *GoGenerator) renderAndFormat(metaData interface{}) (string, error) { 70 | rendered, err := gen.renderBytes(metaData) 71 | if err != nil { 72 | return "", err 73 | } 74 | 75 | formatedAndRendered, err := format.Source(rendered) 76 | if err != nil { 77 | return "", fmt.Errorf( 78 | `While the formting the source code is a error occurd (%v) 79 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 80 | |||||||||||||||||||||||||||||||||||||||Source code|||||||||||||||||||||||||||||||||||||||||||| 81 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 82 | %s 83 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 84 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| 85 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||`, 86 | err, 87 | rendered, 88 | ) 89 | } 90 | 91 | return strings.TrimSpace(string(formatedAndRendered)), nil 92 | } 93 | 94 | func (gen *GoGenerator) RenderBytesData(metaData interface{}) ([]byte, error) { 95 | return gen.renderBytes(metaData) 96 | } 97 | 98 | func (gen *GoGenerator) render(metaData interface{}) (string, error) { 99 | buff, err := gen.populateBuffer(metaData) 100 | if err != nil { 101 | return "", err 102 | } 103 | return buff.String(), nil 104 | } 105 | 106 | func (gen *GoGenerator) renderBytes(metaData interface{}) ([]byte, error) { 107 | buff, err := gen.populateBuffer(metaData) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return buff.Bytes(), nil 112 | } 113 | 114 | func (gen *GoGenerator) populateBuffer(metaData interface{}) (*bytes.Buffer, error) { 115 | buff := new(bytes.Buffer) 116 | if err := gen.tpl.Execute(buff, metaData); err != nil { 117 | return nil, err 118 | } 119 | 120 | return buff, nil 121 | } 122 | 123 | func (gen *GoGenerator) PrepareComment(comment string) []string { 124 | lines := make([]string, 0) 125 | words := strings.Fields(comment) 126 | tmp := words 127 | if len(tmp) > 15 { 128 | for { 129 | if len(tmp) == 0 { 130 | break 131 | } 132 | if len(tmp) > 15 { 133 | lines = append(lines, strings.Join(tmp[:15], " ")) 134 | tmp = tmp[15:] 135 | } else { 136 | lines = append(lines, strings.Join(tmp, " ")) 137 | tmp = make([]string, 0) 138 | } 139 | } 140 | } else { 141 | lines = append(lines, strings.Join(words, " ")) 142 | } 143 | return lines 144 | } 145 | 146 | func (gen *GoGenerator) renderAll(elements interface{}) ([]string, error) { 147 | generators := gen.convertToGenerators(elements) 148 | rendered := make([]string, len(generators)) 149 | for i, generator := range generators { 150 | if generator == nil { 151 | return nil, NewGeneratorErrorString(gen, fmt.Sprintf("generator is nil (%v)", generator)) 152 | } 153 | 154 | s, err := generator.Render() 155 | if err != nil { 156 | return nil, NewGeneratorError(gen, err) 157 | } 158 | 159 | rendered[i] = s 160 | } 161 | 162 | return rendered, nil 163 | } 164 | 165 | func (gen *GoGenerator) convertToGenerators(elements interface{}) []Generator { 166 | typOfElements := reflect.TypeOf(elements) 167 | valueOfElements := reflect.ValueOf(elements) 168 | 169 | if !valueOfElements.IsValid() { 170 | panic(errors.New("elements is invalid")) 171 | } 172 | 173 | if typOfElements.Kind() != reflect.Slice { 174 | panic(errors.New("elements is not a slice")) 175 | } 176 | 177 | generators := make([]Generator, valueOfElements.Len()) 178 | for i := 0; i < valueOfElements.Len(); i++ { 179 | generator, ok := valueOfElements.Index(i).Interface().(Generator) 180 | if !ok { 181 | panic(errors.New("element is not a generator")) 182 | } 183 | generators[i] = generator 184 | } 185 | 186 | return generators 187 | } 188 | -------------------------------------------------------------------------------- /internal/xgenerator/types/go_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "strings" 18 | "testing" 19 | ) 20 | 21 | func newStubGenerator() *stubGenerator { 22 | return new(stubGenerator) 23 | } 24 | 25 | type stubGenerator struct{} 26 | 27 | func (gen *stubGenerator) Render() (string, error) { 28 | return "rendered", nil 29 | } 30 | 31 | func TestGoGenerator_RenderAll(t *testing.T) { 32 | gen := &GoGenerator{} 33 | 34 | tests := []struct { 35 | name string 36 | input interface{} 37 | output string 38 | }{ 39 | { 40 | name: "3 Generators", 41 | input: []*stubGenerator{newStubGenerator(), newStubGenerator(), newStubGenerator()}, 42 | output: "rendered,rendered,rendered", 43 | }, 44 | } 45 | for _, test := range tests { 46 | t.Run(test.name, func(t *testing.T) { 47 | rendered, err := gen.renderAll(test.input) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | s := strings.Join(rendered, ",") 52 | 53 | if s != test.output { 54 | t.Errorf(`unexpected value (actual: "%s", expected: "%s")`, s, test.output) 55 | } 56 | }) 57 | } 58 | } 59 | func TestGoGenerator_RenderAllPanic(t *testing.T) { 60 | gen := &GoGenerator{} 61 | 62 | tests := []struct { 63 | name string 64 | input interface{} 65 | errMessage string 66 | }{ 67 | { 68 | name: "string slice", 69 | input: []string{""}, 70 | errMessage: "element is not a generator", 71 | }, 72 | { 73 | name: "string", 74 | input: "", 75 | errMessage: "elements is not a slice", 76 | }, 77 | { 78 | name: "nil", 79 | input: nil, 80 | errMessage: "elements is invalid", 81 | }, 82 | } 83 | 84 | for _, test := range tests { 85 | t.Run(test.name, func(t *testing.T) { 86 | defer func() { 87 | v := recover() 88 | err, ok := v.(error) 89 | if !ok { 90 | t.Fatalf("value isn't a error (%v)", err) 91 | } 92 | 93 | if err.Error() != test.errMessage { 94 | t.Fatal(err) 95 | } 96 | }() 97 | 98 | _, err := gen.renderAll(test.input) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | }) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /internal/xgenerator/types/goblock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "fmt" 18 | "go/token" 19 | ) 20 | 21 | type GoBlockMetaData struct { 22 | Lines []string 23 | } 24 | 25 | type GoBlockGenerator struct { 26 | MetaData GoBlockMetaData 27 | } 28 | 29 | func (gen *GoBlockGenerator) caller(fnc TypeReference, parameters []string) (string, error) { 30 | 31 | if fnc == nil { 32 | return "", NewGeneratorErrorString(gen, "TypeReference is missing") 33 | } 34 | 35 | if fnc.GetName() == "" { 36 | return "", NewGeneratorErrorString(gen, "function is missing") 37 | } 38 | 39 | if err := ValidateIdent(fnc.GetName()); err != nil { 40 | return "", NewGeneratorError(gen, err) 41 | } 42 | 43 | return fmt.Sprintf(CallerTpl, fnc.GetName(), ValueList(parameters)), nil 44 | } 45 | 46 | func (gen *GoBlockGenerator) Caller(fnc TypeReference, parameters []string) error { 47 | line, err := gen.caller(fnc, parameters) 48 | if err != nil { 49 | return err 50 | } 51 | gen.MetaData.Lines = append(gen.MetaData.Lines, line) 52 | return nil 53 | } 54 | 55 | func (gen *GoBlockGenerator) DefCall(vars []string, fnc TypeReference, params []string) error { 56 | 57 | if fnc == nil { 58 | return NewGeneratorErrorString(gen, "TypeReference is missing") 59 | } 60 | 61 | if fnc.GetName() == "" { 62 | return NewGeneratorErrorString(gen, "function is missing") 63 | } 64 | 65 | if len(vars) == 0 { 66 | return NewGeneratorErrorString(gen, "return values are missing") 67 | } 68 | 69 | if err := ValidateIdents(vars); err != nil { 70 | return NewGeneratorError(gen, err) 71 | } 72 | 73 | if err := ValidateIdent(fnc.GetName()); err != nil { 74 | return NewGeneratorError(gen, err) 75 | } 76 | 77 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefCallTpl, identifierList(vars), fnc.GetName(), ValueList(params))) 78 | return nil 79 | } 80 | 81 | func (gen *GoBlockGenerator) DefAssginCall(vars []string, fnc TypeReference, params []string) error { 82 | 83 | if fnc == nil { 84 | return NewGeneratorErrorString(gen, "TypeReference is missing") 85 | } 86 | 87 | if fnc.GetName() == "" { 88 | return NewGeneratorErrorString(gen, "function is missing") 89 | } 90 | 91 | if len(vars) == 0 { 92 | return NewGeneratorErrorString(gen, "return values are missing") 93 | } 94 | 95 | if err := ValidateIdents(vars); err != nil { 96 | return NewGeneratorError(gen, err) 97 | } 98 | 99 | if err := ValidateIdent(fnc.GetName()); err != nil { 100 | return NewGeneratorError(gen, err) 101 | } 102 | 103 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefAssginCallTpl, identifierList(vars), fnc.GetName(), ValueList(params))) 104 | return nil 105 | } 106 | 107 | func (gen *GoBlockGenerator) DefAppend(vars string, params []string) error { 108 | 109 | if len(vars) == 0 { 110 | return NewGeneratorErrorString(gen, "values are missing") 111 | } 112 | 113 | if err := ValidateIdent(vars); err != nil { 114 | return NewGeneratorError(gen, err) 115 | } 116 | 117 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefAppendTpl, Identifier(vars), ValueList(params))) 118 | return nil 119 | } 120 | 121 | func (gen *GoBlockGenerator) DefMake(vars string, params []string) error { 122 | 123 | if len(vars) == 0 { 124 | return NewGeneratorErrorString(gen, "values are missing") 125 | } 126 | 127 | if err := ValidateIdent(vars); err != nil { 128 | return NewGeneratorError(gen, err) 129 | } 130 | 131 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefCallTpl, Identifier(vars), "make", ValueList(params))) 132 | return nil 133 | } 134 | 135 | func (gen *GoBlockGenerator) SCallWithDefVar(vars []string, fnc TypeReference, params []string) (string, error) { 136 | 137 | if fnc.GetName() == "" { 138 | return "", NewGeneratorErrorString(gen, "function is missing") 139 | } 140 | 141 | if err := ValidateIdent(fnc.GetName()); err != nil { 142 | return "", NewGeneratorError(gen, err) 143 | } 144 | 145 | if len(vars) == 0 { 146 | return "", NewGeneratorErrorString(gen, "return values are missing") 147 | } 148 | 149 | if err := ValidateIdents(vars); err != nil { 150 | return "", NewGeneratorError(gen, err) 151 | } 152 | 153 | return fmt.Sprintf(DefSCallTpl, identifierList(vars), fnc.GetName(), ValueList(params)), nil 154 | } 155 | 156 | func (gen *GoBlockGenerator) SCall(fnc TypeReference, params []string) (string, error) { 157 | 158 | if fnc == nil { 159 | return "", NewGeneratorErrorString(gen, "TypeReference is missing") 160 | } 161 | 162 | if fnc.GetName() == "" { 163 | return "", NewGeneratorErrorString(gen, "function is missing") 164 | } 165 | 166 | if err := ValidateIdent(fnc.GetName()); err != nil { 167 | return "", NewGeneratorError(gen, err) 168 | } 169 | 170 | return fmt.Sprintf(CallerTpl, fnc.GetName(), ValueList(params)), nil 171 | } 172 | 173 | func (gen *GoBlockGenerator) DefNew(varName string, typ TypeReference) error { 174 | 175 | if varName == "" { 176 | return NewGeneratorErrorString(gen, "var is missing") 177 | } 178 | 179 | if err := ValidateIdent(varName); err != nil { 180 | return NewGeneratorError(gen, err) 181 | } 182 | 183 | if typ == nil { 184 | return NewGeneratorErrorString(gen, "TypeReference is missing") 185 | } 186 | 187 | if typ.GetName() == "" { 188 | return NewGeneratorErrorString(gen, "typ is missing") 189 | } 190 | 191 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefNewTpl, Identifier(varName), typ.GetName())) 192 | return nil 193 | } 194 | 195 | func (gen *GoBlockGenerator) DefLongVar(varName, varType string) error { 196 | return gen.defVar(varName, varType, true) 197 | } 198 | 199 | func (gen *GoBlockGenerator) DefShortVar(varName, varType string) error { 200 | return gen.defVar(varName, varType, false) 201 | } 202 | 203 | func (gen *GoBlockGenerator) defVar(varName, varType string, long bool) error { 204 | 205 | var format string 206 | if long { 207 | format = DefVarLongTpl 208 | } else { 209 | format = DefVarShortTpl 210 | } 211 | 212 | if varName == "" { 213 | return NewGeneratorErrorString(gen, "var is missing") 214 | } 215 | 216 | if err := ValidateIdent(varName); err != nil { 217 | return NewGeneratorError(gen, err) 218 | } 219 | 220 | if varType == "" { 221 | return NewGeneratorErrorString(gen, "type of var is missing") 222 | } 223 | 224 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(format, Identifier(varName), varType)) 225 | return nil 226 | } 227 | 228 | func (gen *GoBlockGenerator) DefStruct(varName, typ, propertyOfTyp string) error { 229 | 230 | if varName == "" { 231 | return NewGeneratorErrorString(gen, "var is missing") 232 | } 233 | 234 | if err := ValidateIdent(varName); err != nil { 235 | return NewGeneratorError(gen, err) 236 | } 237 | 238 | if typ == "" { 239 | return NewGeneratorErrorString(gen, "type is missing") 240 | } 241 | 242 | if propertyOfTyp == "" { 243 | return NewGeneratorErrorString(gen, "property of typ is missing") 244 | } 245 | 246 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefStructTpl, Identifier(varName), Identifier(typ), Identifier(propertyOfTyp))) 247 | return nil 248 | } 249 | 250 | func (gen *GoBlockGenerator) DefOperation(varName string, rightSide string, operation token.Token, leftSide string) error { 251 | 252 | if rightSide == "" { 253 | return NewGeneratorErrorString(gen, "right side of operation is missing") 254 | } 255 | 256 | if leftSide == "" { 257 | return NewGeneratorErrorString(gen, "left side of operation is missing") 258 | } 259 | 260 | if err := ValidateIdent(varName); err != nil { 261 | return NewGeneratorError(gen, err) 262 | } 263 | 264 | if err := ValidateOperation(operation); err != nil { 265 | return NewGeneratorError(gen, err) 266 | } 267 | 268 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefOperationTpl, Identifier(varName), Identifier(rightSide), operation.String(), Identifier(leftSide))) 269 | return nil 270 | } 271 | 272 | func (gen *GoBlockGenerator) DefAssert(vars []string, varName string, typ TypeReference) error { 273 | 274 | if len(vars) == 0 { 275 | return NewGeneratorErrorString(gen, "vars is missing") 276 | } 277 | 278 | if err := ValidateIdents(vars); err != nil { 279 | return NewGeneratorError(gen, err) 280 | } 281 | 282 | if varName == "" { 283 | return NewGeneratorErrorString(gen, "var is missing") 284 | } 285 | 286 | if err := ValidateIdent(varName); err != nil { 287 | return NewGeneratorError(gen, err) 288 | } 289 | 290 | if typ.GetName() == "" { 291 | return NewGeneratorErrorString(gen, "type is missing") 292 | } 293 | 294 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefAssertTpl, identifierList(vars), Identifier(varName), typ.GetName())) 295 | return nil 296 | } 297 | 298 | func (gen *GoBlockGenerator) Command(cmd string, params []string) { 299 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(CommandTpl, cmd, identifierList(params))) 300 | } 301 | 302 | func (gen *GoBlockGenerator) Return(params ...[]string) { 303 | 304 | var s string 305 | if params != nil && len(params) == 1 { 306 | s = fmt.Sprintf(DefReturnWithValuesTpl, identifierList(params[0])) 307 | } else { 308 | s = DefReturnTpl 309 | } 310 | 311 | gen.MetaData.Lines = append(gen.MetaData.Lines, s) 312 | } 313 | 314 | func (gen *GoBlockGenerator) StructAssignment(typ, propertyOfTyp string, valueToAssign string) error { 315 | 316 | if typ == "" { 317 | return NewGeneratorErrorString(gen, "type is missing") 318 | } 319 | 320 | if propertyOfTyp == "" { 321 | return NewGeneratorErrorString(gen, "property of typ is missing") 322 | } 323 | 324 | if valueToAssign == "" { 325 | return NewGeneratorErrorString(gen, "value is missing") 326 | } 327 | 328 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(StructAssignTpl, Identifier(typ), Identifier(propertyOfTyp), valueToAssign)) 329 | return nil 330 | } 331 | 332 | func (gen *GoBlockGenerator) DefIfWithOwnScopeBegin(caller string, rightSide string, operation token.Token, leftSide string) error { 333 | if err := gen.validateIf(rightSide, operation, leftSide); err != nil { 334 | return err 335 | } 336 | 337 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(IfStatmentWithOwnScopeTpl, caller, rightSide, operation.String(), leftSide)) 338 | return nil 339 | } 340 | 341 | func (gen *GoBlockGenerator) DefIfBegin(rightSide string, operation token.Token, leftSide string) error { 342 | if err := gen.validateIf(rightSide, operation, leftSide); err != nil { 343 | return err 344 | } 345 | 346 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(IfStatmentTpl, rightSide, operation.String(), leftSide)) 347 | return nil 348 | } 349 | 350 | func (gen *GoBlockGenerator) DefElseIf(rightSide string, operation token.Token, leftSide string) error { 351 | if err := gen.validateIf(rightSide, operation, leftSide); err != nil { 352 | return err 353 | } 354 | 355 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(ElseIfStatmentTpl, rightSide, operation.String(), leftSide)) 356 | return nil 357 | } 358 | 359 | func (gen *GoBlockGenerator) validateIf(rightSide string, operation token.Token, leftSide string) error { 360 | if rightSide == "" { 361 | return NewGeneratorErrorString(gen, "right side of if is missing") 362 | } 363 | 364 | if leftSide == "" { 365 | return NewGeneratorErrorString(gen, "left side of if is missing") 366 | } 367 | 368 | if err := ValidateIdent(rightSide); err != nil { 369 | return NewGeneratorError(gen, err) 370 | } 371 | 372 | if err := ValidateIdent(leftSide); err != nil { 373 | return NewGeneratorError(gen, err) 374 | } 375 | 376 | if err := ValidateIfOperation(operation); err != nil { 377 | return NewGeneratorError(gen, err) 378 | } 379 | return nil 380 | } 381 | 382 | func (gen *GoBlockGenerator) DefRangeBegin(index string, value string, list string) error { 383 | if index == "" { 384 | return NewGeneratorErrorString(gen, "index of range is missing") 385 | } 386 | 387 | if value == "" { 388 | return NewGeneratorErrorString(gen, "value of range is missing") 389 | } 390 | 391 | if err := ValidateIdent(index); err != nil { 392 | return NewGeneratorError(gen, err) 393 | } 394 | 395 | if err := ValidateIdent(value); err != nil { 396 | return NewGeneratorError(gen, err) 397 | } 398 | 399 | if err := ValidateIdent(list); err != nil { 400 | return NewGeneratorError(gen, err) 401 | } 402 | 403 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(RangeTpl, Identifier(index), value, Identifier(list))) 404 | return nil 405 | } 406 | 407 | func (gen *GoBlockGenerator) ReturnCaller(fnc TypeReference, parameters []string) error { 408 | line, err := gen.caller(fnc, parameters) 409 | if err != nil { 410 | return err 411 | } 412 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DefReturnWithValuesTpl, line)) 413 | return nil 414 | } 415 | 416 | func (gen *GoBlockGenerator) Defer(fnc TypeReference, parameters []string) error { 417 | 418 | line, err := gen.caller(fnc, parameters) 419 | if err != nil { 420 | return err 421 | } 422 | gen.MetaData.Lines = append(gen.MetaData.Lines, fmt.Sprintf(DeferTpl, line)) 423 | return nil 424 | } 425 | 426 | func (gen *GoBlockGenerator) Else() { 427 | gen.MetaData.Lines = append(gen.MetaData.Lines, ElseStatmentTpl) 428 | } 429 | 430 | func (gen *GoBlockGenerator) CloseIf() { 431 | gen.MetaData.Lines = append(gen.MetaData.Lines, IfEndTpl) 432 | } 433 | 434 | func (gen *GoBlockGenerator) CloseRange() { 435 | gen.MetaData.Lines = append(gen.MetaData.Lines, RangeEndTpl) 436 | } 437 | 438 | func (gen *GoBlockGenerator) AnonymousGoFunc(generator *AnonymousFuncGenerator) error { 439 | code, err := generator.Render() 440 | if err != nil { 441 | return err 442 | } 443 | gen.MetaData.Lines = append(gen.MetaData.Lines, code+"\n") 444 | return nil 445 | } 446 | 447 | func (gen *GoBlockGenerator) TypeSwitch(switchGenerator SwitchGenerator) error { 448 | code, err := switchGenerator.Render() 449 | if err != nil { 450 | return err 451 | } 452 | gen.MetaData.Lines = append(gen.MetaData.Lines, code+"\n") 453 | return nil 454 | } 455 | 456 | func (gen *GoBlockGenerator) SliceLiteral(sliceGenerator SliceLiteralGenerator) error { 457 | code, err := sliceGenerator.Render() 458 | if err != nil { 459 | return err 460 | } 461 | gen.MetaData.Lines = append(gen.MetaData.Lines, code+"\n") 462 | return nil 463 | } 464 | 465 | func (gen *GoBlockGenerator) InitStruct(statement string, initStructGenerator *InitStructGenerator, pointerReference bool) error { 466 | s, err := initStructGenerator.Render() 467 | if err != nil { 468 | return err 469 | } 470 | 471 | var format string 472 | if pointerReference { 473 | format = fmt.Sprintf("%s &%s \n", statement, s) 474 | } else { 475 | format = fmt.Sprintf("%s %s \n", statement, s) 476 | } 477 | 478 | gen.MetaData.Lines = append(gen.MetaData.Lines, format) 479 | return nil 480 | } 481 | -------------------------------------------------------------------------------- /internal/xgenerator/types/init_struct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | type InitStructFieldTmplValues struct { 17 | Fields []*InitStructField 18 | Typ string 19 | } 20 | 21 | type InitStructField struct { 22 | Name string 23 | Value string 24 | } 25 | 26 | type InitStructGenerator struct { 27 | GoGenerator 28 | MetaData InitStructFieldTmplValues 29 | } 30 | 31 | const initStructTplName string = "initStruct" 32 | const initStructTpl string = ` 33 | {{.Typ}} { 34 | {{range .Fields}} 35 | {{- .Name}}: {{.Value}}, 36 | {{end}} 37 | }` 38 | 39 | func NewInitGoStruct(typ string) (*InitStructGenerator, error) { 40 | initStructGenerator := InitStructGenerator{} 41 | initStructGenerator.TplName = initStructTplName 42 | initStructGenerator.InitTemplate(initStructTpl) 43 | initStructGenerator.MetaData.Typ = typ 44 | return &initStructGenerator, nil 45 | } 46 | 47 | func (gen *InitStructGenerator) AddUnexportedValueToField(name, value string) error { 48 | return gen.addValueToField(UnexportedIdentifier(name), value) 49 | } 50 | 51 | func (gen *InitStructGenerator) AddExportedValueToField(name, value string) error { 52 | return gen.addValueToField(ExportedIdentifier(name), value) 53 | } 54 | 55 | func (gen *InitStructGenerator) addValueToField(name, value string) error { 56 | 57 | if name == "" { 58 | return NewGeneratorErrorString(gen, "unexported field name is missing") 59 | } 60 | 61 | if err := ValidateIdent(name); err != nil { 62 | return NewGeneratorError(gen, err) 63 | } 64 | 65 | if err := ValidateIdent(value); err != nil { 66 | return NewGeneratorError(gen, err) 67 | } 68 | 69 | field := &InitStructField{ 70 | Name: name, 71 | Value: value, 72 | } 73 | 74 | gen.MetaData.Fields = append(gen.MetaData.Fields, field) 75 | 76 | return nil 77 | } 78 | 79 | func (gen *InitStructGenerator) Render() (string, error) { 80 | s, err := gen.render(gen.MetaData) 81 | if err != nil { 82 | return s, NewGeneratorError(gen, err) 83 | } 84 | return s, err 85 | } 86 | -------------------------------------------------------------------------------- /internal/xgenerator/types/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | const interfaceNameTpl string = "interface" 17 | 18 | const interfaceTpl string = ` 19 | {{ if .HeaderComment }} // {{.HeaderComment }} {{end}} 20 | 21 | type {{ .Name }} interface { 22 | {{range $i, $Prototype := .Prototypes}} 23 | {{if index $Prototype.Comment }} 24 | {{range $i, $line := index $Prototype.Comment }} 25 | // {{ index $line }} 26 | {{- end}} 27 | {{- end}} 28 | {{ index $Prototype.Name }}({{- index $Prototype.Params }}) {{if index $Prototype.Returns }} ({{- index $Prototype.Returns }}) {{end}} 29 | {{end}} 30 | }` 31 | 32 | type InterfaceMetadata struct { 33 | Name string 34 | HeaderComment string 35 | Prototypes []InterfacePrototypeMetadata 36 | } 37 | 38 | type InterfacePrototypeMetadata struct { 39 | Name string 40 | Params string 41 | Returns string 42 | Comment []string 43 | } 44 | 45 | type InterfaceGenerator struct { 46 | GoGenerator 47 | InterfaceMetadata InterfaceMetadata 48 | } 49 | 50 | func NewGoInterface(name string) (*InterfaceGenerator, error) { 51 | 52 | interfaceGenerator := InterfaceGenerator{} 53 | if name == "" { 54 | return nil, NewGeneratorErrorString(interfaceGenerator, "name of interface is missing") 55 | } 56 | 57 | if err := ValidateIdent(name); err != nil { 58 | return nil, NewGeneratorError(interfaceGenerator, err) 59 | } 60 | 61 | interfaceGenerator.InterfaceMetadata = InterfaceMetadata{ 62 | Name: ExportedIdentifier(name), 63 | } 64 | 65 | interfaceGenerator.TplName = interfaceNameTpl 66 | interfaceGenerator.InitTemplate(interfaceTpl) 67 | return &interfaceGenerator, nil 68 | } 69 | 70 | func (gen *InterfaceGenerator) Prototype(name string, parameters []*Parameter, returns []TypeReference, comment string) error { 71 | if name == "" { 72 | return NewGeneratorErrorString(gen, "name of prototype is missing") 73 | } 74 | 75 | if err := ValidateIdent(name); err != nil { 76 | return NewGeneratorError(gen, err) 77 | } 78 | 79 | if err := ValidateParameters(parameters); err != nil { 80 | return NewGeneratorError(gen, err) 81 | } 82 | 83 | prototype := InterfacePrototypeMetadata{ 84 | Name: ExportedIdentifier(name), 85 | Params: paramList(parameters), 86 | Returns: typeList(returns), 87 | } 88 | 89 | if comment != "" { 90 | prototype.Comment = gen.PrepareComment(comment) 91 | } 92 | 93 | gen.InterfaceMetadata.Prototypes = append(gen.InterfaceMetadata.Prototypes, prototype) 94 | 95 | return nil 96 | } 97 | 98 | func (gen *InterfaceGenerator) Render() (string, error) { 99 | s, err := gen.renderAndFormat(gen.InterfaceMetadata) 100 | if err != nil { 101 | return s, NewGeneratorError(gen, err) 102 | } 103 | return s, err 104 | } 105 | -------------------------------------------------------------------------------- /internal/xgenerator/types/interface_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types_test 15 | 16 | import ( 17 | "github.com/donutloop/xservice/internal/xgenerator/types" 18 | 19 | "testing" 20 | ) 21 | 22 | func TestNewInterfaceAndRender(t *testing.T) { 23 | interfaceGenerator, err := types.NewGoInterface("Stringer") 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | 29 | err = interfaceGenerator.Prototype("split", 30 | []*types.Parameter{ 31 | types.NewParameterWithTypeReference("s", types.String), 32 | types.NewParameterWithTypeReference("sep", types.String), 33 | }, 34 | []types.TypeReference{ 35 | types.String, 36 | types.String, 37 | }, 38 | "", 39 | ) 40 | 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | 46 | renderedInterface, err := interfaceGenerator.Render() 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | 52 | expectedInterface := `type Stringer interface { 53 | Split(s string, sep string) (string, string) 54 | }` 55 | if expectedInterface != renderedInterface { 56 | t.Errorf(`Unexpected interface definition (Actual: "%s", Expected: "%s")`, renderedInterface, expectedInterface) 57 | return 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /internal/xgenerator/types/method.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | type MethodGeneratorMetaData struct { 17 | Name string 18 | Lines []string 19 | Returns string 20 | Params string 21 | MethodOfTyp string 22 | TypShortcut string 23 | Fnc string 24 | Comment []string 25 | } 26 | 27 | type MethodGenerator struct { 28 | GoGenerator 29 | GoBlockGenerator 30 | TypeFuncMetadata MethodGeneratorMetaData 31 | } 32 | 33 | const methodTplName string = "method" 34 | const methodTpl string = ` 35 | {{if .Comment }} 36 | {{range $i, $line := .Comment}} 37 | // {{index $line -}} 38 | {{end}} 39 | {{- end}} 40 | func ({{ .TypShortcut }} {{ .MethodOfTyp }}) {{ .Name }}({{ .Params }}) {{if .Returns }} ({{- .Returns }}) {{end}} { 41 | {{range $i, $line := .Lines}} 42 | {{- $line | safe }} 43 | {{- end -}} 44 | }` 45 | 46 | func NewGoMethod(TypShortcut, methodOfTyp, name string, parameters []*Parameter, returns []TypeReference, comment string) (*MethodGenerator, error) { 47 | 48 | gen := &MethodGenerator{} 49 | if methodOfTyp == "" { 50 | return nil, NewGeneratorErrorString(gen, "method binding is missing") 51 | } 52 | 53 | if TypShortcut == "" { 54 | return nil, NewGeneratorErrorString(gen, "typ shortcut of method is missing") 55 | } 56 | 57 | if name == "" { 58 | return nil, NewGeneratorErrorString(gen, "name of method is missing") 59 | } 60 | 61 | if err := ValidateIdent(name); err != nil { 62 | return nil, NewGeneratorError(gen, err) 63 | } 64 | 65 | if err := ValidateIdent(TypShortcut); err != nil { 66 | return nil, NewGeneratorError(gen, err) 67 | } 68 | 69 | if err := ValidateParameters(parameters); err != nil { 70 | return nil, NewGeneratorError(gen, err) 71 | } 72 | 73 | gen.TypeFuncMetadata = MethodGeneratorMetaData{ 74 | Name: Identifier(name), 75 | Params: paramList(parameters), 76 | MethodOfTyp: methodOfTyp, 77 | TypShortcut: TypShortcut, 78 | } 79 | 80 | if len(returns) > 0 { 81 | gen.TypeFuncMetadata.Returns = typeList(returns) 82 | } 83 | 84 | if comment != "" { 85 | gen.TypeFuncMetadata.Comment = gen.PrepareComment(comment) 86 | } 87 | 88 | gen.TplName = methodTplName 89 | gen.InitTemplate(methodTpl) 90 | 91 | return gen, nil 92 | } 93 | 94 | func (gen *MethodGenerator) Render() (string, error) { 95 | gen.TypeFuncMetadata.Lines = gen.GoBlockGenerator.MetaData.Lines 96 | s, err := gen.renderAndFormat(gen.TypeFuncMetadata) 97 | if err != nil { 98 | return s, NewGeneratorError(gen, err) 99 | } 100 | return s, err 101 | } 102 | -------------------------------------------------------------------------------- /internal/xgenerator/types/prototype.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | const prototypeTplName string = "prototype" 17 | const prototypeTpl string = ` 18 | {{if .Comment }} 19 | {{range $i, $line := .Comment}} 20 | // {{index $line }} 21 | {{- end}} 22 | {{- end}} 23 | type {{ .Name }} func({{ .Params }}) ({{ .Returns }})` 24 | 25 | type PrototypeMetadata struct { 26 | Name string 27 | Params string 28 | Returns string 29 | Comment []string 30 | } 31 | 32 | type PrototypeGenerator struct { 33 | GoGenerator 34 | PrototypeMetadata PrototypeMetadata 35 | } 36 | 37 | func NewGoFuncPrototype(name string, parameters []*Parameter, returns []TypeReference, comment string) (*PrototypeGenerator, error) { 38 | 39 | prototypeGenerator := PrototypeGenerator{} 40 | if name == "" { 41 | return nil, NewGeneratorErrorString(prototypeGenerator, "name of prototype is missing") 42 | } 43 | 44 | if err := ValidateIdent(name); err != nil { 45 | return nil, NewGeneratorError(prototypeGenerator, err) 46 | } 47 | 48 | if err := ValidateParameters(parameters); err != nil { 49 | return nil, NewGeneratorError(prototypeGenerator, err) 50 | } 51 | 52 | gen := &PrototypeGenerator{ 53 | PrototypeMetadata: PrototypeMetadata{ 54 | Name: ExportedIdentifier(name), 55 | Params: paramList(parameters), 56 | Returns: typeList(returns), 57 | }, 58 | } 59 | if comment != "" { 60 | gen.PrototypeMetadata.Comment = gen.PrepareComment(comment) 61 | } 62 | gen.TplName = prototypeTplName 63 | gen.InitTemplate(prototypeTpl) 64 | 65 | return gen, nil 66 | } 67 | 68 | func (gen *PrototypeGenerator) Render() (string, error) { 69 | s, err := gen.renderAndFormat(gen.PrototypeMetadata) 70 | if err != nil { 71 | return s, NewGeneratorError(gen, err) 72 | } 73 | return s, err 74 | } 75 | -------------------------------------------------------------------------------- /internal/xgenerator/types/prototype_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types_test 15 | 16 | import ( 17 | "github.com/donutloop/xservice/internal/xgenerator/types" 18 | "testing" 19 | ) 20 | 21 | func TestNewGoPrototypeAndRender(t *testing.T) { 22 | 23 | prototypeGenerator, err := types.NewGoFuncPrototype( 24 | "split", 25 | []*types.Parameter{ 26 | types.NewParameterWithTypeReference("s", types.String), 27 | types.NewParameterWithTypeReference("sep", types.String), 28 | }, 29 | []types.TypeReference{ 30 | types.String, 31 | types.String, 32 | }, 33 | "", 34 | ) 35 | 36 | if err != nil { 37 | t.Error(err) 38 | return 39 | } 40 | 41 | renderedPrototype, err := prototypeGenerator.Render() 42 | if err != nil { 43 | t.Error(err) 44 | return 45 | } 46 | 47 | expectedPrototype := "type Split func(s string, sep string) (string, string)" 48 | if expectedPrototype != renderedPrototype { 49 | t.Errorf(`Unexpected prototype definition (Actual: "%s", Expected: "%s")`, renderedPrototype, expectedPrototype) 50 | return 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /internal/xgenerator/types/slice_literal.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import "strconv" 17 | 18 | // Template of slice literal 19 | const sliceLiteralTpl string = ` 20 | {{ .Name }} := [{{ if .Len }}{{ .Len }}{{ end }}]{{ .Typ }}{ 21 | {{range $i, $Value := .Values}} 22 | {{- $Value}}, 23 | {{end}} 24 | }` 25 | const sliceLiteralTplName string = "sliceLiteral" 26 | 27 | type SliceLiteralMetadata struct { 28 | Name string 29 | Values []string 30 | Typ string 31 | Len string 32 | } 33 | 34 | type SliceLiteralGenerator struct { 35 | GoGenerator 36 | SliceMetaData SliceLiteralMetadata 37 | } 38 | 39 | func NewGoSliceLiteral(varName string, typeReference TypeReference, len int) (*SliceLiteralGenerator, error) { 40 | 41 | sliceGen := SliceLiteralGenerator{} 42 | 43 | if varName == "" { 44 | return nil, NewGeneratorErrorString(sliceGen, "name of sliceliteral is empty") 45 | } 46 | 47 | if err := ValidateIdent(varName); err != nil { 48 | return nil, NewGeneratorError(sliceGen, err) 49 | } 50 | 51 | if typeReference == nil { 52 | return nil, NewGeneratorErrorString(sliceGen, "TypeReference is missing") 53 | 54 | } 55 | 56 | if typeReference.GetName() == "" { 57 | return nil, NewGeneratorErrorString(sliceGen, "type is missing") 58 | } 59 | 60 | sliceGen.SliceMetaData = SliceLiteralMetadata{ 61 | Name: varName, 62 | Typ: typeReference.GetName(), 63 | Len: strconv.Itoa(len), 64 | } 65 | 66 | sliceGen.TplName = sliceLiteralTplName 67 | sliceGen.InitTemplate(sliceLiteralTpl) 68 | 69 | return &sliceGen, nil 70 | } 71 | 72 | func (gen *SliceLiteralGenerator) Append(s string) { 73 | gen.SliceMetaData.Values = append(gen.SliceMetaData.Values, s) 74 | } 75 | 76 | func (gen *SliceLiteralGenerator) Render() (string, error) { 77 | s, err := gen.renderAndFormat(gen.SliceMetaData) 78 | if err != nil { 79 | return s, NewGeneratorError(gen, err) 80 | } 81 | return s, err 82 | } 83 | -------------------------------------------------------------------------------- /internal/xgenerator/types/struct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | const structWithoutPkgAndGroupTplName string = "structWithoutPkgAndGroup" 17 | const structWithoutPkgAndGroupTpl string = ` 18 | {{if .Comment }} 19 | {{range $i, $line := .Comment}} 20 | // {{index $line }} 21 | {{- end}} 22 | {{- end}} 23 | {{.Name}} struct { 24 | {{range .Fields}} 25 | {{- .Name}} {{.Type}} {{.TagMeta | safe }} {{if .CommentOfProperty }}// {{.CommentOfProperty }}{{end}} 26 | {{end -}} 27 | }` 28 | 29 | const structWithoutPkgTplName string = "structWithoutPkg" 30 | const structWithoutPkgTpl string = ` 31 | {{if .Comment }} 32 | {{range $i, $line := .Comment}} 33 | // {{index $line }} 34 | {{- end}} 35 | {{- end}} 36 | type {{.Name}} struct { 37 | {{range .Fields}} 38 | {{- .Name}} {{.Type}} {{.TagMeta | safe }} {{if .CommentOfProperty }}// {{.CommentOfProperty }}{{end}} 39 | {{end}} 40 | }` 41 | 42 | type StructTmplValues struct { 43 | Name string 44 | Comment []string 45 | Fields []*StructFieldTmplValues 46 | Methods []*MethodGenerator 47 | } 48 | 49 | type StructFieldTmplValues struct { 50 | Name string 51 | Type string 52 | TagMeta string 53 | CommentOfProperty string 54 | } 55 | 56 | type StructGenerator struct { 57 | GoGenerator 58 | Group bool 59 | StructMetaData StructTmplValues 60 | } 61 | 62 | func NewGoStruct(name string, withoutGroup bool, exported bool) (*StructGenerator, error) { 63 | 64 | structGenerator := StructGenerator{} 65 | if name == "" { 66 | return nil, NewGeneratorErrorString(structGenerator, "name of struct is missing") 67 | } 68 | 69 | if err := ValidateIdent(name); err != nil { 70 | return nil, NewGeneratorError(structGenerator, err) 71 | } 72 | 73 | structGenerator.StructMetaData = StructTmplValues{} 74 | 75 | if exported { 76 | structGenerator.StructMetaData.Name = ExportedIdentifier(name) 77 | } else { 78 | structGenerator.StructMetaData.Name = UnexportedIdentifier(name) 79 | } 80 | 81 | if withoutGroup { 82 | structGenerator.TplName = structWithoutPkgTplName 83 | structGenerator.InitTemplate(structWithoutPkgTpl) 84 | } else { 85 | structGenerator.TplName = structWithoutPkgAndGroupTplName 86 | structGenerator.InitTemplate(structWithoutPkgAndGroupTpl) 87 | } 88 | 89 | structGenerator.Group = withoutGroup 90 | 91 | return &structGenerator, nil 92 | } 93 | 94 | func (gen *StructGenerator) Type(typ TypeReference, comment string) error { 95 | 96 | if typ == nil { 97 | return NewGeneratorErrorString(gen, "TypeReference is missing") 98 | } 99 | 100 | if typ.GetName() == "" { 101 | return NewGeneratorErrorString(gen, "typ of exported field is missing") 102 | } 103 | 104 | field := &StructFieldTmplValues{ 105 | Type: typ.GetName(), 106 | CommentOfProperty: comment, 107 | } 108 | 109 | gen.StructMetaData.Fields = append(gen.StructMetaData.Fields, field) 110 | return nil 111 | } 112 | 113 | func (gen *StructGenerator) AddExportedField(name string, typ TypeReference, comment string) error { 114 | 115 | if name == "" { 116 | return NewGeneratorErrorString(gen, "exported field name is missing") 117 | } 118 | 119 | if typ == nil { 120 | return NewGeneratorErrorString(gen, "TypeReference is missing") 121 | } 122 | 123 | if typ.GetName() == "" { 124 | return NewGeneratorErrorString(gen, "typ of exported field is missing") 125 | } 126 | 127 | if err := ValidateIdent(name); err != nil { 128 | return NewGeneratorError(gen, err) 129 | } 130 | 131 | field := &StructFieldTmplValues{ 132 | Name: ExportedIdentifier(name), 133 | Type: typ.GetName(), 134 | CommentOfProperty: comment, 135 | } 136 | 137 | gen.StructMetaData.Fields = append(gen.StructMetaData.Fields, field) 138 | return nil 139 | } 140 | 141 | func (gen *StructGenerator) Composition(typ TypeReference) error { 142 | 143 | if typ == nil { 144 | return NewGeneratorErrorString(gen, "TypeReference is missing") 145 | } 146 | 147 | if typ.GetName() == "" { 148 | return NewGeneratorErrorString(gen, "typ of exported field is missing") 149 | } 150 | 151 | field := &StructFieldTmplValues{ 152 | Type: typ.GetName(), 153 | } 154 | 155 | gen.StructMetaData.Fields = append(gen.StructMetaData.Fields, field) 156 | return nil 157 | } 158 | 159 | func (gen *StructGenerator) AddUnexportedField(name string, typ TypeReference, comment string) error { 160 | 161 | if name == "" { 162 | return NewGeneratorErrorString(gen, "unexported field name is missing") 163 | } 164 | 165 | if typ == nil { 166 | return NewGeneratorErrorString(gen, "TypeReference is missing") 167 | } 168 | 169 | if typ.GetName() == "" { 170 | return NewGeneratorErrorString(gen, "typ of unexported field is missing") 171 | } 172 | 173 | if err := ValidateIdent(name); err != nil { 174 | return NewGeneratorError(gen, err) 175 | } 176 | 177 | field := &StructFieldTmplValues{ 178 | Name: UnexportedIdentifier(name), 179 | Type: typ.GetName(), 180 | CommentOfProperty: comment, 181 | } 182 | 183 | gen.StructMetaData.Fields = append(gen.StructMetaData.Fields, field) 184 | 185 | return nil 186 | } 187 | 188 | func (gen *StructGenerator) AddMethod(fg ...*MethodGenerator) { 189 | gen.StructMetaData.Methods = append(gen.StructMetaData.Methods, fg...) 190 | } 191 | 192 | func (gen *StructGenerator) GetMethods() []*MethodGenerator { 193 | return gen.StructMetaData.Methods 194 | } 195 | 196 | func (gen *StructGenerator) Render() (string, error) { 197 | if !gen.Group { 198 | s, err := gen.render(gen.StructMetaData) 199 | if err != nil { 200 | return s, NewGeneratorError(gen, err) 201 | } 202 | return s, err 203 | } 204 | s, err := gen.renderAndFormat(gen.StructMetaData) 205 | if err != nil { 206 | return s, NewGeneratorError(gen, err) 207 | } 208 | return s, err 209 | } 210 | -------------------------------------------------------------------------------- /internal/xgenerator/types/struct_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types_test 15 | 16 | import ( 17 | "github.com/donutloop/xservice/internal/xgenerator/types" 18 | "testing" 19 | ) 20 | 21 | func TestNewGoStructAndRender(t *testing.T) { 22 | 23 | structGenerator, err := types.NewGoStruct("Strings", true, true) 24 | if err != nil { 25 | t.Error(err) 26 | return 27 | } 28 | 29 | structGenerator.AddExportedField("raw", types.String, "") 30 | 31 | renderedStruct, err := structGenerator.Render() 32 | if err != nil { 33 | t.Error(err) 34 | return 35 | } 36 | 37 | expectedStruct := `type Strings struct { 38 | Raw string 39 | }` 40 | if expectedStruct != renderedStruct { 41 | t.Errorf(`Unexpected struct definition (Actual: "%s", Expected: "%s")`, renderedStruct, expectedStruct) 42 | return 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/xgenerator/types/switch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | const typeSwitchTpl string = ` 17 | switch {{if .Typed }}{{.Typed }}:={{end}} {{.Var}}{{if .Typed }}.(type){{end}} { 18 | {{range $i, $CaseData := .Cases -}} 19 | case {{ $CaseData.Type }}: 20 | {{range $i, $line := $CaseData.Code -}} 21 | {{ $line }} 22 | {{- end}} 23 | {{- end -}} 24 | {{ if .DefaultCase }} 25 | default: 26 | {{range $i, $line := .DefaultCase.Code -}} 27 | {{ $line }} 28 | {{- end -}} 29 | {{- end -}} 30 | } 31 | ` 32 | 33 | type SwitchGeneratorMetaData struct { 34 | Cases []Case 35 | DefaultCase DefaultCase 36 | Var string 37 | Typed string 38 | } 39 | 40 | type SwitchGenerator struct { 41 | GoGenerator 42 | SwitchMetaData SwitchGeneratorMetaData 43 | } 44 | 45 | type Case struct { 46 | Type string 47 | Code []string 48 | } 49 | 50 | type DefaultCase struct { 51 | Code []string 52 | } 53 | 54 | func NewSwitchGenerator(varName string) (*SwitchGenerator, error) { 55 | gen := &SwitchGenerator{} 56 | if varName == "" { 57 | return nil, NewGeneratorErrorString(gen, "var name is missing") 58 | } 59 | if err := ValidateIdent(varName); err != nil { 60 | return nil, NewGeneratorError(gen, err) 61 | } 62 | gen.SwitchMetaData.Var = varName 63 | gen.TplName = "type_switch" 64 | gen.InitTemplate(typeSwitchTpl) 65 | return gen, nil 66 | } 67 | 68 | func (gen *SwitchGenerator) UseAssertion(ok bool) { 69 | if ok { 70 | gen.SwitchMetaData.Typed = "o" 71 | return 72 | } 73 | } 74 | 75 | func (gen *SwitchGenerator) Case(caseGenerator CaseGenerator) { 76 | gen.SwitchMetaData.Cases = append(gen.SwitchMetaData.Cases, Case{Type: caseGenerator.CaseMetaData.Typ, Code: caseGenerator.GoBlockGenerator.MetaData.Lines}) 77 | } 78 | 79 | func (gen *SwitchGenerator) Default(caseDefaultGenerator DefaultCaseGenerator) { 80 | gen.SwitchMetaData.DefaultCase = DefaultCase{Code: caseDefaultGenerator.GoBlockGenerator.MetaData.Lines} 81 | } 82 | 83 | func (gen *SwitchGenerator) Render() (string, error) { 84 | s, err := gen.render(gen.SwitchMetaData) 85 | if err != nil { 86 | return s, NewGeneratorError(gen, err) 87 | } 88 | return s, err 89 | } 90 | 91 | type CaseGeneratorMetaData struct { 92 | Lines []string 93 | Typ string 94 | } 95 | 96 | type CaseGenerator struct { 97 | GoGenerator 98 | GoBlockGenerator 99 | CaseMetaData CaseGeneratorMetaData 100 | } 101 | 102 | const CaseTpl string = ` 103 | case {{ .Typ }}: 104 | {{range $i, $line := .Lines -}} 105 | {{ $line }} 106 | {{- end}} 107 | ` 108 | 109 | func NewCaseGenerator(value string) (*CaseGenerator, error) { 110 | gen := &CaseGenerator{} 111 | if value == "" { 112 | return nil, NewGeneratorErrorString(gen, "value is missing") 113 | } 114 | if err := ValidateIdent(value); err != nil { 115 | return nil, NewGeneratorError(gen, err) 116 | } 117 | gen.CaseMetaData.Typ = value 118 | gen.TplName = "case" 119 | gen.InitTemplate(CaseTpl) 120 | return gen, nil 121 | } 122 | 123 | func (gen *CaseGenerator) Render() (string, error) { 124 | gen.CaseMetaData.Lines = gen.GoBlockGenerator.MetaData.Lines 125 | s, err := gen.render(gen.CaseMetaData) 126 | if err != nil { 127 | return s, NewGeneratorError(gen, err) 128 | } 129 | return s, err 130 | } 131 | 132 | const DefaultCaseTpl string = ` 133 | default: 134 | {{range $i, $line := .Lines -}} 135 | {{ $line }} 136 | {{- end}} 137 | ` 138 | 139 | type DefaultCaseGeneratorMetaData struct { 140 | Lines []string 141 | } 142 | 143 | type DefaultCaseGenerator struct { 144 | GoGenerator 145 | GoBlockGenerator 146 | CaseMetaData DefaultCaseGeneratorMetaData 147 | } 148 | 149 | func NewDefaultCaseGenerator() (*DefaultCaseGenerator, error) { 150 | gen := &DefaultCaseGenerator{} 151 | gen.TplName = "defaultCase" 152 | gen.InitTemplate(DefaultCaseTpl) 153 | return gen, nil 154 | } 155 | 156 | func (gen *DefaultCaseGenerator) Render() (string, error) { 157 | gen.CaseMetaData.Lines = gen.GoBlockGenerator.MetaData.Lines 158 | s, err := gen.render(gen.CaseMetaData) 159 | if err != nil { 160 | return s, NewGeneratorError(gen, err) 161 | } 162 | return s, err 163 | } 164 | -------------------------------------------------------------------------------- /internal/xgenerator/types/type_reference.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package types 15 | 16 | import ( 17 | "bytes" 18 | "errors" 19 | "fmt" 20 | "path" 21 | "reflect" 22 | "runtime" 23 | "strings" 24 | ) 25 | 26 | // Import represent an individual imported package. 27 | type Import interface { 28 | // GetPackage returns the go import package, like reflect.Type.PkgPath() 29 | GetPackage() string 30 | // GetAlias returns an alias string to refer to the import package, or the 31 | // empty string to omit an import alias. 32 | GetAlias() string 33 | } 34 | 35 | // TypeReference represent a specific reference (either an interface, function, struct or global) 36 | type TypeReference interface { 37 | // GetImports returns the imports required to use this type. A struct, for example, 38 | // collects all the imports for its fields and itself. 39 | GetImports() []Import 40 | // GetName returns the go-syntax name of the type. 41 | GetName() string 42 | } 43 | 44 | // ImportSpec implements Import to represent an imported go package 45 | type ImportSpec struct { 46 | Package string 47 | Alias string 48 | Qualified bool 49 | } 50 | 51 | // getQualifier returns the fully qualified package (e.g. bytes.) for use in a qualified 52 | // declared type 53 | func (i *ImportSpec) getQualifier() string { 54 | if !i.Qualified { 55 | return "" 56 | } 57 | 58 | var buff bytes.Buffer 59 | if i.Alias != "" { 60 | buff.WriteString(i.Alias) 61 | } else { 62 | // the package may contain slashes, so only write the base name of the package, 63 | // not the full package 64 | buff.WriteString(path.Base(i.Package)) 65 | } 66 | buff.WriteString(".") 67 | 68 | return buff.String() 69 | } 70 | 71 | // GetAlias returns the alias associated with the package 72 | func (i *ImportSpec) GetAlias() string { 73 | return i.Alias 74 | } 75 | 76 | // GetPackage returns the package 77 | func (i *ImportSpec) GetPackage() string { 78 | return i.Package 79 | } 80 | 81 | // UnqualifiedPrefix The prefix for type aliases that will be interpreted as unqualified 82 | const UnqualifiedPrefix = "_unqualified" 83 | 84 | func stringPointerFunc() *string { 85 | s := "" 86 | return &s 87 | } 88 | 89 | func boolPointerFunc() *bool { 90 | b := false 91 | return &b 92 | } 93 | 94 | func intPointerFunc() *int { 95 | n := 0 96 | return &n 97 | } 98 | 99 | func int8PointerFunc() *int8 { 100 | n := int8(0) 101 | return &n 102 | } 103 | 104 | func int16PointerFunc() *int16 { 105 | n := int16(0) 106 | return &n 107 | } 108 | 109 | func int32PointerFunc() *int32 { 110 | n := int32(0) 111 | return &n 112 | } 113 | 114 | func int64PointerFunc() *int64 { 115 | n := int64(0) 116 | return &n 117 | } 118 | 119 | func uintPointerFunc() *uint { 120 | n := uint(0) 121 | return &n 122 | } 123 | 124 | func uint8PointerFunc() *uint8 { 125 | n := uint8(0) 126 | return &n 127 | } 128 | 129 | func uint16PointerFunc() *uint16 { 130 | n := uint16(0) 131 | return &n 132 | } 133 | 134 | func uint32PointerFunc() *uint32 { 135 | n := uint32(0) 136 | return &n 137 | } 138 | 139 | func uint64PointerFunc() *uint64 { 140 | n := uint64(0) 141 | return &n 142 | } 143 | 144 | var ( 145 | // String A TypeReference for string 146 | String = TypeReferenceFromInstance("") 147 | // String A TypeReference for string pointer 148 | StringPointer = TypeReferenceFromInstance(stringPointerFunc()) 149 | // Bool A TypeReference for bool 150 | Bool = TypeReferenceFromInstance(false) 151 | // Bool A TypeReference for bool pointer 152 | BoolPointer = TypeReferenceFromInstance(boolPointerFunc()) 153 | // Int A TypeReference for int 154 | Int = TypeReferenceFromInstance(0) 155 | // Int A TypeReference for int 156 | IntPointer = TypeReferenceFromInstance(intPointerFunc()) 157 | // Int8 A TypeReference for int8 158 | Int8 = TypeReferenceFromInstance(int8(0)) 159 | // Int8 A TypeReference for int8 pointer 160 | Int8Pointer = TypeReferenceFromInstance(int8PointerFunc) 161 | // Int16 A TypeReference for int16 162 | Int16 = TypeReferenceFromInstance(int16(0)) 163 | // Int16 A TypeReference for int16 pointer 164 | Int16Pointer = TypeReferenceFromInstance(int16PointerFunc) 165 | // Int32 A TypeReference for int32 166 | Int32 = TypeReferenceFromInstance(int32(0)) 167 | // Int32 A TypeReference for int32 pointer 168 | Int32Pointer = TypeReferenceFromInstance(int32PointerFunc) 169 | // Int64 A TypeReference for int64 170 | Int64 = TypeReferenceFromInstance(int64(0)) 171 | // Int64 A TypeReference for int64 pointer 172 | Int64Pointer = TypeReferenceFromInstance(int64PointerFunc()) 173 | // Int A TypeReference for int 174 | Uint = TypeReferenceFromInstance(uint(0)) 175 | // Int A TypeReference for int 176 | UintPointer = TypeReferenceFromInstance(uintPointerFunc()) 177 | // Int8 A TypeReference for int8 178 | Uint8 = TypeReferenceFromInstance(uint8(0)) 179 | // Int8 A TypeReference for int8 pointer 180 | Uint8Pointer = TypeReferenceFromInstance(uint8PointerFunc) 181 | // Int16 A TypeReference for int16 182 | Uint16 = TypeReferenceFromInstance(uint16(0)) 183 | // Int16 A TypeReference for int16 pointer 184 | Uint16Pointer = TypeReferenceFromInstance(uint16PointerFunc) 185 | // Int32 A TypeReference for int32 186 | Uint32 = TypeReferenceFromInstance(uint32(0)) 187 | // Int32 A TypeReference for int32 pointer 188 | Uint32Pointer = TypeReferenceFromInstance(uint32PointerFunc) 189 | // Int64 A TypeReference for int64 190 | Uint64 = TypeReferenceFromInstance(uint64(0)) 191 | // Int64 A TypeReference for int64 pointer 192 | Uint64Pointer = TypeReferenceFromInstance(uint64PointerFunc()) 193 | 194 | // todo missing pointers 195 | // Uintptr A TypeReference for uintptr 196 | Uintptr = TypeReferenceFromInstance(uintptr(0)) 197 | // Float32 A TypeReference for float32 198 | Float32 = TypeReferenceFromInstance(float32(0)) 199 | // Float64 A TypeReference for float64 200 | Float64 = TypeReferenceFromInstance(float64(0)) 201 | // Complex64 A TypeReference for complex64 202 | Complex64 = TypeReferenceFromInstance(complex64(0)) 203 | // Complex128 A TypeReference for complex128 204 | Complex128 = TypeReferenceFromInstance(complex128(0)) 205 | // Byte A TypeReference for byte 206 | Byte = TypeReferenceFromInstanceWithCustomName(uint8(0), "byte") 207 | // Rune A TypeReference for rune 208 | Rune = TypeReferenceFromInstanceWithCustomName(int32(0), "rune") 209 | // Error A TypeReference for error 210 | Error = TypeReferenceFromInstanceWithCustomName(errors.New(""), "error") 211 | ) 212 | 213 | func UnsafePointerReference(p string) string { 214 | return fmt.Sprintf("*%s", p) 215 | } 216 | 217 | type Parameter struct { 218 | NameOfParameter string 219 | Typ TypeReference 220 | } 221 | 222 | func NewParameterWithTypeReference(name string, t TypeReference) *Parameter { 223 | return &Parameter{ 224 | NameOfParameter: name, 225 | Typ: t, 226 | } 227 | } 228 | 229 | func NewParameterWithTypeReferenceFromInstance(name string, t interface{}) *Parameter { 230 | return &Parameter{ 231 | NameOfParameter: name, 232 | Typ: TypeReferenceFromInstance(t), 233 | } 234 | } 235 | 236 | func NewParameterWithUnsafeTypeReference(name, t string) *Parameter { 237 | return &Parameter{ 238 | NameOfParameter: name, 239 | Typ: NewUnsafeTypeReference(t), 240 | } 241 | } 242 | 243 | // TypeReferenceFromInstance creates a TypeReference from an instance of a variable 244 | func TypeReferenceFromInstance(t interface{}) TypeReference { 245 | return newTypeReferenceFromInstance(t, "") 246 | } 247 | 248 | // NewUnsafeTypeReference creates a (Unsafe) TypeReference from an string id 249 | func NewUnsafeTypeReference(t string) TypeReference { 250 | return &unsafeTypeReferenceValue{Name: t} 251 | } 252 | 253 | // TypeReferenceFromInstanceWithAlias creates a TypeReference from an instance of a variable 254 | // with the given package alias 255 | func TypeReferenceFromInstanceWithAlias(t interface{}, alias string) TypeReference { 256 | return newTypeReferenceFromInstance(t, alias) 257 | } 258 | 259 | // TypeReferenceFromInstanceWithCustomName creates a TypeReference from an instance of a variable 260 | // with the given custom name, for use of a type alias's name rather than the underlying 261 | // reflect type. 262 | func TypeReferenceFromInstanceWithCustomName(t interface{}, name string) TypeReference { 263 | typeRef := &typeReferenceWithCustomName{ 264 | TypeReference: newTypeReferenceFromInstance(t, ""), 265 | name: name, 266 | } 267 | 268 | return typeRef 269 | } 270 | 271 | type typeReferenceWithCustomName struct { 272 | TypeReference 273 | name string 274 | } 275 | 276 | func (t *typeReferenceWithCustomName) GetName() string { 277 | return t.name 278 | } 279 | 280 | func newTypeReferenceFromInstance(t interface{}, alias string) TypeReference { 281 | reflectType := reflect.TypeOf(t) 282 | if reflectType == nil { 283 | panic("Invalid nil instance without associated type") 284 | } 285 | 286 | if reflectType.Kind() == reflect.Func { 287 | return newTypeReferenceFromFunction(t, alias) 288 | } 289 | 290 | return newTypeReferenceFromValue(t, alias) 291 | } 292 | 293 | type typeReferenceMap struct { 294 | KeyType TypeReference 295 | ValueType TypeReference 296 | prefix string 297 | } 298 | 299 | func newTypeReferenceFromMap(t interface{}, prefix string) TypeReference { 300 | refType := reflect.TypeOf(t) 301 | 302 | return &typeReferenceMap{ 303 | KeyType: newTypeReferenceFromInstance(reflect.New(refType.Key()).Elem().Interface(), ""), 304 | ValueType: newTypeReferenceFromInstance(reflect.New(refType.Elem()).Elem().Interface(), ""), 305 | prefix: prefix, 306 | } 307 | } 308 | 309 | func (t *typeReferenceMap) GetImports() []Import { 310 | imports := make([]Import, 0) 311 | imports = append(imports, t.KeyType.GetImports()...) 312 | imports = append(imports, t.ValueType.GetImports()...) 313 | return imports 314 | } 315 | 316 | func (t *typeReferenceMap) GetName() string { 317 | return fmt.Sprintf("%smap[%s]%s", t.prefix, t.KeyType.GetName(), t.ValueType.GetName()) 318 | } 319 | 320 | type typeReferenceValue struct { 321 | Import *ImportSpec 322 | Name string 323 | prefix string 324 | } 325 | 326 | func newTypeReferenceFromValue(t interface{}, alias string) TypeReference { 327 | refType := reflect.TypeOf(t) 328 | result := new(typeReferenceValue) 329 | result.prefix, refType = dereferenceType("", refType) 330 | 331 | switch refType.Kind() { 332 | case reflect.Interface: 333 | fallthrough 334 | case reflect.Struct: 335 | result.Import = &ImportSpec{ 336 | Qualified: !strings.HasPrefix(refType.Name(), UnqualifiedPrefix), 337 | Package: refType.PkgPath(), 338 | Alias: alias, 339 | } 340 | case reflect.Map: 341 | return newTypeReferenceFromMap(reflect.New(refType).Elem().Interface(), result.prefix) 342 | } 343 | 344 | result.Name = strings.TrimPrefix(refType.Name(), UnqualifiedPrefix) 345 | 346 | return result 347 | } 348 | 349 | func dereferenceType(prefix string, refType reflect.Type) (string, reflect.Type) { 350 | for { 351 | if refType.Kind() == reflect.Ptr { 352 | refType = refType.Elem() 353 | // interfaces are already pointers, so don't need to add prefix 354 | if refType.Kind() != reflect.Interface { 355 | prefix += "*" 356 | } 357 | } else if refType.Kind() == reflect.Slice || refType.Kind() == reflect.Array { 358 | prefix += "[]" 359 | refType = refType.Elem() 360 | } else if refType.Kind() == reflect.Chan { 361 | prefix += refType.ChanDir().String() + " " 362 | refType = refType.Elem() 363 | } else { 364 | break 365 | } 366 | } 367 | 368 | return prefix, refType 369 | } 370 | 371 | func (t *typeReferenceValue) GetImports() []Import { 372 | return []Import{t.Import} 373 | } 374 | 375 | func (t *typeReferenceValue) GetName() string { 376 | var buff bytes.Buffer 377 | buff.WriteString(t.prefix) 378 | if t.Import != nil { 379 | buff.WriteString(t.Import.getQualifier()) 380 | } 381 | buff.WriteString(t.Name) 382 | return buff.String() 383 | } 384 | 385 | type typeReferenceFunc struct { 386 | Import *ImportSpec 387 | Name string 388 | } 389 | 390 | func newTypeReferenceFromFunction(t interface{}, alias string) TypeReference { 391 | // split up the function's name from its package path 392 | n := runtime.FuncForPC(reflect.ValueOf(t).Pointer()).Name() 393 | ndxOfLastDot := strings.LastIndex(n, ".") 394 | 395 | return &typeReferenceFunc{ 396 | Import: &ImportSpec{ 397 | Qualified: true, 398 | Package: n[:ndxOfLastDot], 399 | Alias: alias, 400 | }, 401 | Name: n[ndxOfLastDot+1:], 402 | } 403 | } 404 | 405 | func (t *typeReferenceFunc) GetImports() []Import { 406 | return []Import{t.Import} 407 | } 408 | 409 | func (t *typeReferenceFunc) GetName() string { 410 | var buff bytes.Buffer 411 | if t.Import != nil { 412 | buff.WriteString(t.Import.getQualifier()) 413 | } 414 | buff.WriteString(t.Name) 415 | return buff.String() 416 | } 417 | 418 | type unsafeTypeReferenceValue struct { 419 | Name string 420 | } 421 | 422 | func (t *unsafeTypeReferenceValue) GetImports() []Import { 423 | return nil 424 | } 425 | 426 | func (t *unsafeTypeReferenceValue) GetName() string { 427 | return t.Name 428 | } 429 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/fileset.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donutloop/xservice/5182803737bae1b4ecb3ed6728c9983e1a0f4554/internal/xproto/typesmap/testdata/fileset.pb -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. All rights reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package testdata 19 | 20 | //go:generate protoc --descriptor_set_out=fileset.pb --include_imports --include_source_info ./service.proto 21 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/importer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package internal.gen.typemap.testdata.importer; 4 | 5 | import "root_pkg.proto"; 6 | 7 | message ImporterMsg { 8 | root_pkg.RootMsg a = 1; 9 | 10 | message ImporterInner {} 11 | } 12 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/public_importer.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package internal.gen.typemap.testdata.public_importer; 4 | 5 | import public "root_pkg.proto"; 6 | import public "importer.proto"; 7 | 8 | message PublicImporterMsgA { 9 | importer.ImporterMsg a = 1; 10 | } 11 | 12 | message PublicImporterMsgB { 13 | root_pkg.RootMsg a = 1; 14 | } 15 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/public_reimporter.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package internal.gen.typemap.testdata.public_reimporter; 4 | 5 | import public "public_importer.proto"; 6 | 7 | message PublicReimporterMsg { 8 | public_importer.PublicImporterMsgA a = 1; 9 | public_importer.PublicImporterMsgB b = 2; 10 | root_pkg.RootMsg c = 3; 11 | importer.ImporterMsg d = 4; 12 | } 13 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/root_pkg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package internal.gen.typemap.testdata.root_pkg; 4 | 5 | // RootMsg leading 6 | message RootMsg {} 7 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/testdata/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package internal.gen.typemap.testdata.public_reimporter; 4 | 5 | import public "public_reimporter.proto"; 6 | 7 | message ServiceMsg { 8 | public_importer.PublicImporterMsgA a = 1; 9 | public_importer.PublicImporterMsgB b = 2; 10 | root_pkg.RootMsg c = 3; 11 | importer.ImporterMsg d = 4; 12 | } 13 | 14 | message Parent { 15 | message NestedOuter{ 16 | message NestedInner{} 17 | } 18 | } 19 | 20 | service EmptyService {} 21 | 22 | service ServiceWithOneMethod{ 23 | // Method1 leading 24 | rpc Method1(root_pkg.RootMsg) returns (importer.ImporterMsg); 25 | // Method1 trailing 26 | } 27 | 28 | // ServiceWithManyMethods leading 29 | service ServiceWithManyMethods{ 30 | // Method1 leading 31 | rpc Method1(root_pkg.RootMsg) returns (importer.ImporterMsg); 32 | // Method2 leading 33 | rpc Method2(Parent) returns (Parent.NestedOuter); 34 | // Method2 trailing 35 | 36 | rpc Method3(importer.ImporterMsg.ImporterInner) returns (Parent.NestedOuter.NestedInner); 37 | } 38 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/typesmap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package typemap 19 | 20 | import ( 21 | "github.com/golang/protobuf/protoc-gen-go/descriptor" 22 | "github.com/pkg/errors" 23 | ) 24 | 25 | type Registry struct { 26 | allFiles []*descriptor.FileDescriptorProto 27 | filesByName map[string]*descriptor.FileDescriptorProto 28 | 29 | // Mapping of fully-qualified names to their definitions 30 | messagesByProtoName map[string]*MessageDefinition 31 | } 32 | 33 | func New(files []*descriptor.FileDescriptorProto) *Registry { 34 | r := &Registry{ 35 | allFiles: files, 36 | filesByName: make(map[string]*descriptor.FileDescriptorProto), 37 | messagesByProtoName: make(map[string]*MessageDefinition), 38 | } 39 | 40 | // First, index the file descriptors by name. We need this so 41 | // messageDefsForFile can correctly scan imports. 42 | for _, f := range files { 43 | r.filesByName[f.GetName()] = f 44 | } 45 | 46 | // Next, index all the message definitions by their fully-qualified proto 47 | // names. 48 | for _, f := range files { 49 | defs := messageDefsForFile(f, r.filesByName) 50 | for name, def := range defs { 51 | r.messagesByProtoName[name] = def 52 | } 53 | } 54 | return r 55 | } 56 | 57 | func (r *Registry) FileComments(file *descriptor.FileDescriptorProto) (DefinitionComments, error) { 58 | return commentsAtPath([]int32{packagePath}, file), nil 59 | } 60 | 61 | func (r *Registry) ServiceComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto) (DefinitionComments, error) { 62 | for i, s := range file.Service { 63 | if s == svc { 64 | path := []int32{servicePath, int32(i)} 65 | return commentsAtPath(path, file), nil 66 | } 67 | } 68 | return DefinitionComments{}, errors.Errorf("service not found in file") 69 | } 70 | 71 | func (r *Registry) MethodComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto) (DefinitionComments, error) { 72 | for i, s := range file.Service { 73 | if s == svc { 74 | path := []int32{servicePath, int32(i)} 75 | for j, m := range s.Method { 76 | if m == method { 77 | path = append(path, serviceMethodPath, int32(j)) 78 | return commentsAtPath(path, file), nil 79 | } 80 | } 81 | } 82 | } 83 | return DefinitionComments{}, errors.Errorf("service not found in file") 84 | } 85 | 86 | func (r *Registry) MethodInputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition { 87 | return r.messagesByProtoName[method.GetInputType()] 88 | } 89 | 90 | func (r *Registry) MethodOutputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition { 91 | return r.messagesByProtoName[method.GetOutputType()] 92 | } 93 | 94 | func (r *Registry) MessageDefinition(name string) *MessageDefinition { 95 | return r.messagesByProtoName[name] 96 | } 97 | 98 | type MessageDefinition struct { 99 | // Descriptor is is the DescriptorProto defining the message. 100 | Descriptor *descriptor.DescriptorProto 101 | // File is the File that the message was defined in. Or, if it has been 102 | // publicly imported, what File was that import performed in? 103 | File *descriptor.FileDescriptorProto 104 | // Parent is the parent message, if this was defined as a nested message. If 105 | // this was defiend at the top level, parent is nil. 106 | Parent *MessageDefinition 107 | // Comments describes the comments surrounding a message's definition. If it 108 | // was publicly imported, then these comments are from the actual source file, 109 | // not the file that the import was performed in. 110 | Comments DefinitionComments 111 | 112 | // path is the 'SourceCodeInfo' path. See the documentation for 113 | // github.com/golang/protobuf/protoc-gen-go/descriptor.SourceCodeInfo for an 114 | // explanation of its format. 115 | path []int32 116 | } 117 | 118 | // ProtoName returns the dot-delimited, fully-qualified protobuf name of the 119 | // message. 120 | func (m *MessageDefinition) ProtoName() string { 121 | prefix := "." 122 | if pkg := m.File.GetPackage(); pkg != "" { 123 | prefix += pkg + "." 124 | } 125 | 126 | if lineage := m.Lineage(); len(lineage) > 0 { 127 | for _, parent := range lineage { 128 | prefix += parent.Descriptor.GetName() + "." 129 | } 130 | } 131 | 132 | return prefix + m.Descriptor.GetName() 133 | } 134 | 135 | // Lineage returns m's parental chain all the way back up to a top-level message 136 | // definition. The first element of the returned slice is the highest-level 137 | // parent. 138 | func (m *MessageDefinition) Lineage() []*MessageDefinition { 139 | var parents []*MessageDefinition 140 | for p := m.Parent; p != nil; p = p.Parent { 141 | parents = append([]*MessageDefinition{p}, parents...) 142 | } 143 | return parents 144 | } 145 | 146 | // descendants returns all the submessages defined within m, and all the 147 | // descendants of those, recursively. 148 | func (m *MessageDefinition) descendants() []*MessageDefinition { 149 | descendants := make([]*MessageDefinition, 0) 150 | for i, child := range m.Descriptor.NestedType { 151 | path := append(m.path, []int32{messageMessagePath, int32(i)}...) 152 | childDef := &MessageDefinition{ 153 | Descriptor: child, 154 | File: m.File, 155 | Parent: m, 156 | Comments: commentsAtPath(path, m.File), 157 | path: path, 158 | } 159 | descendants = append(descendants, childDef) 160 | descendants = append(descendants, childDef.descendants()...) 161 | } 162 | return descendants 163 | } 164 | 165 | // messageDefsForFile gathers a mapping of fully-qualified protobuf names to 166 | // their definitions. It scans a singles file at a time. It requires a mapping 167 | // of .proto file names to their definitions in order to correctly handle 168 | // 'import public' declarations; this mapping should include all files 169 | // transitively imported by f. 170 | func messageDefsForFile(f *descriptor.FileDescriptorProto, filesByName map[string]*descriptor.FileDescriptorProto) map[string]*MessageDefinition { 171 | byProtoName := make(map[string]*MessageDefinition) 172 | // First, gather all the messages defined at the top level. 173 | for i, d := range f.MessageType { 174 | path := []int32{messagePath, int32(i)} 175 | def := &MessageDefinition{ 176 | Descriptor: d, 177 | File: f, 178 | Parent: nil, 179 | Comments: commentsAtPath(path, f), 180 | path: path, 181 | } 182 | 183 | byProtoName[def.ProtoName()] = def 184 | // Next, all nested message definitions. 185 | for _, child := range def.descendants() { 186 | byProtoName[child.ProtoName()] = child 187 | } 188 | } 189 | 190 | // Finally, all messages imported publicly. 191 | for _, depIdx := range f.PublicDependency { 192 | depFileName := f.Dependency[depIdx] 193 | depFile := filesByName[depFileName] 194 | depDefs := messageDefsForFile(depFile, filesByName) 195 | for _, def := range depDefs { 196 | imported := &MessageDefinition{ 197 | Descriptor: def.Descriptor, 198 | File: f, 199 | Parent: def.Parent, 200 | Comments: commentsAtPath(def.path, depFile), 201 | path: def.path, 202 | } 203 | byProtoName[imported.ProtoName()] = imported 204 | } 205 | } 206 | 207 | return byProtoName 208 | } 209 | 210 | // DefinitionComments contains the comments surrounding a definition in a 211 | // protobuf file. 212 | // 213 | // These follow the rules described by protobuf: 214 | // 215 | // A series of line comments appearing on consecutive lines, with no other 216 | // tokens appearing on those lines, will be treated as a single comment. 217 | // 218 | // leading_detached_comments will keep paragraphs of comments that appear 219 | // before (but not connected to) the current element. Each paragraph, 220 | // separated by empty lines, will be one comment element in the repeated 221 | // field. 222 | // 223 | // Only the comment content is provided; comment markers (e.g. //) are 224 | // stripped out. For block comments, leading whitespace and an asterisk 225 | // will be stripped from the beginning of each line other than the first. 226 | // Newlines are included in the output. 227 | // 228 | // Examples: 229 | // 230 | // optional int32 foo = 1; // Comment attached to foo. 231 | // // Comment attached to bar. 232 | // optional int32 bar = 2; 233 | // 234 | // optional string baz = 3; 235 | // // Comment attached to baz. 236 | // // Another line attached to baz. 237 | // 238 | // // Comment attached to qux. 239 | // // 240 | // // Another line attached to qux. 241 | // optional double qux = 4; 242 | // 243 | // // Detached comment for corge. This is not leading or trailing comments 244 | // // to qux or corge because there are blank lines separating it from 245 | // // both. 246 | // 247 | // // Detached comment for corge paragraph 2. 248 | // 249 | // optional string corge = 5; 250 | // /* Block comment attached 251 | // * to corge. Leading asterisks 252 | // * will be removed. */ 253 | // /* Block comment attached to 254 | // * grault. */ 255 | // optional int32 grault = 6; 256 | // 257 | // // ignored detached comments. 258 | type DefinitionComments struct { 259 | Leading string 260 | Trailing string 261 | LeadingDetached []string 262 | } 263 | 264 | func commentsAtPath(path []int32, sourceFile *descriptor.FileDescriptorProto) DefinitionComments { 265 | if sourceFile.SourceCodeInfo == nil { 266 | // The compiler didn't provide us with comments. 267 | return DefinitionComments{} 268 | } 269 | 270 | for _, loc := range sourceFile.SourceCodeInfo.Location { 271 | if pathEqual(path, loc.Path) { 272 | return DefinitionComments{ 273 | Leading: loc.GetLeadingComments(), 274 | LeadingDetached: loc.GetLeadingDetachedComments(), 275 | Trailing: loc.GetTrailingComments(), 276 | } 277 | } 278 | } 279 | return DefinitionComments{} 280 | } 281 | 282 | func pathEqual(path1, path2 []int32) bool { 283 | if len(path1) != len(path2) { 284 | return false 285 | } 286 | for i, v := range path1 { 287 | if path2[i] != v { 288 | return false 289 | } 290 | } 291 | return true 292 | } 293 | 294 | const ( 295 | // tag numbers in FileDescriptorProto 296 | packagePath = 2 // package 297 | messagePath = 4 // message_type 298 | enumPath = 5 // enum_type 299 | servicePath = 6 // service 300 | // tag numbers in DescriptorProto 301 | messageFieldPath = 2 // field 302 | messageMessagePath = 3 // nested_type 303 | messageEnumPath = 4 // enum_type 304 | messageOneofPath = 8 // oneof_decl 305 | // tag numbers in ServiceDescriptorProto 306 | serviceNamePath = 1 // name 307 | serviceMethodPath = 2 // method 308 | serviceOptionsPath = 3 // options 309 | // tag numbers in MethodDescriptorProto 310 | methodNamePath = 1 // name 311 | methodInputPath = 2 // input_type 312 | methodOutputPath = 3 // output_type 313 | ) 314 | -------------------------------------------------------------------------------- /internal/xproto/typesmap/typesmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package typemap 19 | 20 | import ( 21 | "io/ioutil" 22 | "path/filepath" 23 | "testing" 24 | 25 | "github.com/golang/protobuf/proto" 26 | "github.com/golang/protobuf/protoc-gen-go/descriptor" 27 | "github.com/stretchr/testify/assert" 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | func loadTestPb(t *testing.T) []*descriptor.FileDescriptorProto { 32 | f, err := ioutil.ReadFile(filepath.Join("testdata", "fileset.pb")) 33 | require.NoError(t, err, "unable to read testdata protobuf file") 34 | 35 | set := new(descriptor.FileDescriptorSet) 36 | err = proto.Unmarshal(f, set) 37 | require.NoError(t, err, "unable to unmarshal testdata protobuf file") 38 | 39 | return set.File 40 | } 41 | 42 | func protoFile(files []*descriptor.FileDescriptorProto, name string) *descriptor.FileDescriptorProto { 43 | for _, f := range files { 44 | if filepath.Base(f.GetName()) == name { 45 | return f 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | func service(f *descriptor.FileDescriptorProto, name string) *descriptor.ServiceDescriptorProto { 52 | for _, s := range f.Service { 53 | if s.GetName() == name { 54 | return s 55 | } 56 | } 57 | return nil 58 | } 59 | 60 | func method(s *descriptor.ServiceDescriptorProto, name string) *descriptor.MethodDescriptorProto { 61 | for _, m := range s.Method { 62 | if m.GetName() == name { 63 | return m 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func TestNewRegistry(t *testing.T) { 70 | files := loadTestPb(t) 71 | file := protoFile(files, "service.proto") 72 | service := service(file, "ServiceWithManyMethods") 73 | 74 | reg := New(files) 75 | 76 | comments, err := reg.ServiceComments(file, service) 77 | require.NoError(t, err, "unable to load service comments") 78 | assert.Equal(t, " ServiceWithManyMethods leading\n", comments.Leading) 79 | 80 | method1 := method(service, "Method1") 81 | require.NotNil(t, method1) 82 | 83 | method1Input := reg.MethodInputDefinition(method1) 84 | require.NotNil(t, method1Input) 85 | assert.Equal(t, "RootMsg", method1Input.Descriptor.GetName()) 86 | } 87 | -------------------------------------------------------------------------------- /internal/xproto/version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | package xproto 15 | 16 | const Version = "v0.1.0" 17 | -------------------------------------------------------------------------------- /internal/xproto/xprotoutil/xprotoutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 XService, All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"). You may not 4 | // use this file except in compliance with the License. A copy of the License is 5 | // located at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // or in the "license" file accompanying this file. This file is distributed on 10 | // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 11 | // express or implied. See the License for the specific language governing 12 | // permissions and limitations under the License. 13 | 14 | // This file contains some code from https://github.com/twitchtv/twirp/: 15 | // Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. 16 | // https://github.com/twitchtv/twirp/ 17 | 18 | package xprotoutil 19 | 20 | import ( 21 | "github.com/golang/protobuf/protoc-gen-go/descriptor" 22 | "strings" 23 | ) 24 | 25 | // goPackageName returns the Go package name to use in the generated Go file. 26 | // The result explicitly reports whether the name came from an option go_package 27 | // statement. If explicit is false, the name was derived from the protocol 28 | // buffer's package statement or the input file name. 29 | func GoPackageName(f *descriptor.FileDescriptorProto) (name string, explicit bool) { 30 | 31 | // Does the file have a "go_package" option? 32 | if _, pkg, ok := GoPackageOption(f); ok { 33 | return pkg, true 34 | } 35 | 36 | // Does the file have a package clause? 37 | if pkg := f.GetPackage(); pkg != "" { 38 | return pkg, false 39 | } 40 | // Use the file base name. 41 | return f.GetName(), false 42 | } 43 | 44 | // goPackageOption interprets the file's go_package option. 45 | // If there is no go_package, it returns ("", "", false). 46 | // If there's a simple name, it returns ("", pkg, true). 47 | // If the option implies an import path, it returns (impPath, pkg, true). 48 | func GoPackageOption(f *descriptor.FileDescriptorProto) (impPath, pkg string, ok bool) { 49 | 50 | pkg = f.GetOptions().GetGoPackage() 51 | if pkg == "" { 52 | return 53 | } 54 | ok = true 55 | // The presence of a slash implies there's an import path. 56 | slash := strings.LastIndex(pkg, "/") 57 | if slash < 0 { 58 | return 59 | } 60 | impPath, pkg = pkg, pkg[slash+1:] 61 | 62 | // A semicolon-delimited suffix overrides the package name. 63 | sc := strings.IndexByte(impPath, ';') 64 | if sc < 0 { 65 | return 66 | } 67 | impPath, pkg = impPath[:sc], impPath[sc+1:] 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donutloop/xservice/5182803737bae1b4ecb3ed6728c9983e1a0f4554/logo.png --------------------------------------------------------------------------------