├── .github ├── dependabot.yml └── workflows │ └── go.yml ├── .golangci.yml ├── CODEOWNERS ├── LICENSE ├── NOTICE ├── README.md ├── benchmarks_test.go ├── examples └── structs │ ├── gen_struct.go │ └── main.go ├── gen_struct_test.go ├── go.mod ├── go.sum ├── internal └── node │ ├── node.go │ └── node_test.go ├── pubsub-gen ├── internal │ ├── end2end │ │ ├── end2end_test.go │ │ ├── generate.sh │ │ ├── generated_traverser_test.go │ │ └── test_types.go │ ├── generator │ │ ├── code_writer.go │ │ ├── path_generator.go │ │ └── traverser_generator.go │ └── inspector │ │ ├── linker.go │ │ ├── linker_test.go │ │ ├── package_parser.go │ │ ├── package_parser_test.go │ │ ├── struct_fetcher.go │ │ └── struct_fetcher_test.go ├── main.go └── setters │ └── setters.go ├── pubsub.go ├── pubsub_test.go └── vendor ├── github.com └── poy │ └── onpar │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── diff │ ├── diff.go │ └── doc.go │ ├── expect │ ├── README.md │ └── expect.go │ ├── matchers │ ├── README.md │ ├── always.go │ ├── and.go │ ├── be_above.go │ ├── be_below.go │ ├── be_closed.go │ ├── be_false.go │ ├── be_nil.go │ ├── be_true.go │ ├── chain.go │ ├── contain.go │ ├── contain_sub_string.go │ ├── differ.go │ ├── end_with.go │ ├── equal.go │ ├── fetch.go │ ├── have_cap.go │ ├── have_key.go │ ├── have_len.go │ ├── have_occurred.go │ ├── is_nil.go │ ├── match_json.go │ ├── match_regexp.go │ ├── not.go │ ├── or.go │ ├── panic.go │ ├── receive.go │ ├── start_with.go │ └── via_polling.go │ └── onpar.go └── modules.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | 10 | # Maintain dependencies for Go Modules 11 | - package-ecosystem: "gomod" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-go@v5 15 | with: 16 | go-version-file: go.mod 17 | - run: go test -v -race ./... 18 | 19 | vet: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - uses: actions/setup-go@v5 24 | with: 25 | go-version-file: go.mod 26 | - run: go vet ./... 27 | 28 | lint: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: actions/setup-go@v5 33 | with: 34 | go-version-file: go.mod 35 | - uses: golangci/golangci-lint-action@v7.0.0 36 | with: 37 | args: --config .golangci.yml 38 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | enable: 4 | # Checks for non-ASCII identifiers 5 | - asciicheck 6 | # Computes and checks the cyclomatic complexity of functions. 7 | - gocyclo 8 | # Inspects source code for security problems. 9 | - gosec 10 | settings: 11 | gosec: 12 | excludes: 13 | # Ignore controversial integer overflow warnings. 14 | - G115 15 | exclusions: 16 | generated: lax 17 | presets: 18 | - comments 19 | - common-false-positives 20 | - legacy 21 | - std-error-handling 22 | paths: 23 | # Exclude generated test files that would otherwise fail. 24 | - gen_struct_test.go 25 | - examples/structs/gen_struct.go 26 | - pubsub-gen/internal/end2end/generated_traverser_test.go 27 | - third_party$ 28 | - builtin$ 29 | - examples$ 30 | issues: 31 | # Disable max issues per linter. 32 | max-issues-per-linter: 0 33 | # Disable max same issues. 34 | max-same-issues: 0 35 | formatters: 36 | exclusions: 37 | generated: lax 38 | paths: 39 | - third_party$ 40 | - builtin$ 41 | - examples$ 42 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cloudfoundry/wg-app-runtime-platform-logging-and-metrics-approvers 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. 2 | 3 | This project contains software that is Copyright (c) 2017 Andrew Poydence 4 | 5 | This project is licensed to you under the Apache License, Version 2.0 (the "License"). 6 | You may not use this project except in compliance with the License. 7 | 8 | This project may include a number of subcomponents with separate copyright notices 9 | and license terms. Your use of these subcomponents is subject to the terms and 10 | conditions of the subcomponent's license, as noted in the LICENSE file. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![pubsub][pubsub-logo] 3 | 4 | [![GoDoc][go-doc-badge]][go-doc] 5 | 6 | PubSub publishes data to subscriptions. However, it can do so much more than 7 | just push some data to a subscription. Each subscription is placed in a tree. 8 | When data is published, it traverses the tree and finds each interested 9 | subscription. This allows for sophisticated filters and routing. 10 | 11 | If you have any questions, or want to get attention for a PR or issue please reach out on the [#logging-and-metrics channel in the cloudfoundry slack](https://cloudfoundry.slack.com/archives/CUW93AF3M) 12 | 13 | ### Installation 14 | 15 | ```bash 16 | go get code.cloudfoundry.org/go-pubsub 17 | ``` 18 | 19 | ### Subscription Trees 20 | 21 | A subscription tree is a collection of subscriptions that are organized based 22 | on what data they want published to them. When subscribing, a path is 23 | provided to give directions to PubSub about where to store the subscription 24 | and what data should be published to it. 25 | 26 | So for example, say there are three subscriptions with the following paths: 27 | 28 | | Name | Path | 29 | |-------|-------------------| 30 | | sub-1 | `["a", "b", "c"]` | 31 | | sub-2 | `["a", "b", "d"]` | 32 | | sub-3 | `["a", "b"]` | 33 | 34 | After both subscriptions have been registered PubSub will have the following 35 | subscription tree: 36 | ``` 37 | a 38 | | 39 | b <-(sub-3) 40 | / \ 41 | (sub-1)-> c d <-(sub-2) 42 | ``` 43 | 44 | To better draw out each's subscriptions view of the tree: 45 | 46 | ##### Sub-1 47 | 48 | ``` 49 | a 50 | | 51 | b 52 | / 53 | (sub-1)-> c 54 | ``` 55 | 56 | ##### Sub-2 57 | 58 | ``` 59 | a 60 | | 61 | b 62 | \ 63 | d <-(sub-2) 64 | ``` 65 | 66 | ##### Sub-3 67 | 68 | ``` 69 | a 70 | | 71 | b <-(sub-3) 72 | ``` 73 | 74 | So to give a few exapmles of how data could be published: 75 | 76 | ###### Single path 77 | 78 | ``` 79 | a 80 | | 81 | b <-(sub-3) 82 | \ 83 | d <-(sub-2) 84 | ``` 85 | 86 | In this example both `sub-2` and `sub-3` would have the data written to it. 87 | 88 | ###### Multi-Path 89 | 90 | ``` 91 | a 92 | | 93 | b <-(sub-3) 94 | / \ 95 | (sub-1)-> c d <-(sub-2) 96 | ``` 97 | 98 | In this example all `sub-1`, `sub-2` and `sub-3` would have the data written 99 | to it. 100 | 101 | ##### Shorter Path 102 | 103 | ``` 104 | a 105 | | 106 | b <-(sub-3) 107 | ``` 108 | 109 | In this example only `sub-3` would have the data written to it. 110 | 111 | ##### Other Path 112 | 113 | ``` 114 | x 115 | | 116 | y 117 | ``` 118 | 119 | In this example, no subscriptions would have data written to them. 120 | 121 | ### Simple Example: 122 | 123 | ```go 124 | ps := pubsub.New() 125 | subscription := func(name string) pubsub.SubscriptionFunc { 126 | return func(data interface{}) { 127 | fmt.Printf("%s -> %v\n", name, data) 128 | } 129 | } 130 | 131 | ps.Subscribe(subscription("sub-1"), []string{"a", "b", "c"}) 132 | ps.Subscribe(subscription("sub-2"), []string{"a", "b", "d"}) 133 | ps.Subscribe(subscription("sub-3"), []string{"a", "b", "e"}) 134 | 135 | ps.Publish("data-1", pubsub.LinearTreeTraverser([]string{"a", "b"})) 136 | ps.Publish("data-2", pubsub.LinearTreeTraverser([]string{"a", "b", "c"})) 137 | ps.Publish("data-3", pubsub.LinearTreeTraverser([]string{"a", "b", "d"})) 138 | ps.Publish("data-3", pubsub.LinearTreeTraverser([]string{"x", "y"})) 139 | 140 | // Output: 141 | // sub-1 -> data-2 142 | // sub-2 -> data-3 143 | ``` 144 | 145 | In this example the `LinearTreeTraverser` is used to traverse the tree of 146 | subscriptions. When an interested subscription is found (in this case `sub-1` 147 | and `sub-2` for `data-2` and `data-3` respectively), the subscription is 148 | handed the data. 149 | 150 | More complex examples can be found in the 151 | [examples](https://code.cloudfoundry.org/go-pubsub/tree/master/examples) 152 | directory. 153 | 154 | ### TreeTraversers 155 | 156 | A `TreeTraverser` is used to traverse the subscription tree and find what 157 | subscriptions should have the data published to them. There are a few 158 | implementations provided, however it is likely a user will need to implement 159 | their own to suit their data. 160 | 161 | When creating a `TreeTraverser` it is important to note how the data is 162 | structured. A `TreeTraverser` must be deterministic and ideally stateless. The 163 | order the data is parsed and returned (via `Traverse()`) must align with the 164 | given path of `Subscribe()`. 165 | 166 | This means if the `TreeTraverser` intends to look at field A, then B, and then 167 | finally C, then the subscription path must be A, B and then C (and not B, A, C 168 | or something). 169 | 170 | ### Subscriptions 171 | 172 | A `Subscription` is used when publishing data. The given path is used to 173 | determine it's placement in the subscription tree. 174 | 175 | ### Code Generation 176 | 177 | The tree traversers and subscriptions are quite complicated. Laying out a tree 178 | structure is not something humans are going to find natural. Therefore a 179 | [generator](https://github.com/cloudfoundry-incubator/go-pubsub/tree/master/pubsub-gen) 180 | is provided for structs. 181 | 182 | The struct is inspected (at `go generate` time) and creates the tree layout 183 | code. There is a provided 184 | [example](https://github.com/cloudfoundry-incubator/go-pubsub/tree/master/examples/structs). 185 | 186 | [pubsub-logo]: https://raw.githubusercontent.com/cloudfoundry/go-pubsub/gh-pages/pubsub-logo.png 187 | [go-doc-badge]: https://godoc.org/code.cloudfoundry.org/go-pubsub?status.svg 188 | [go-doc]: https://godoc.org/code.cloudfoundry.org/go-pubsub 189 | -------------------------------------------------------------------------------- /benchmarks_test.go: -------------------------------------------------------------------------------- 1 | package pubsub_test 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "testing" 7 | 8 | "code.cloudfoundry.org/go-pubsub" 9 | ) 10 | 11 | func BenchmarkPublishing(b *testing.B) { 12 | b.StopTimer() 13 | p := pubsub.New() 14 | for i := 0; i < 100; i++ { 15 | _, f := newSpySubscrption() 16 | p.Subscribe(f, pubsub.WithPath(randPath())) 17 | } 18 | b.StartTimer() 19 | 20 | for i := 0; i < b.N; i++ { 21 | p.Publish("data", pubsub.LinearTreeTraverser(randPath())) 22 | } 23 | } 24 | 25 | func BenchmarkPublishingStructs(b *testing.B) { 26 | b.StopTimer() 27 | p := pubsub.New() 28 | for i := 0; i < 100; i++ { 29 | _, f := newSpySubscrption() 30 | p.Subscribe(f, pubsub.WithPath(randPath())) 31 | } 32 | data := randStructs() 33 | st := StructTraverser{} 34 | b.StartTimer() 35 | 36 | for i := 0; i < b.N; i++ { 37 | p.Publish(data[i%len(data)], st.traverse) 38 | } 39 | } 40 | 41 | func BenchmarkSubscriptions(b *testing.B) { 42 | b.StopTimer() 43 | p := pubsub.New() 44 | b.StartTimer() 45 | 46 | b.RunParallel(func(b *testing.PB) { 47 | i := rand.Int() //nolint:gosec 48 | for b.Next() { 49 | _, f := newSpySubscrption() 50 | unsub := p.Subscribe(f, pubsub.WithPath(randPath())) 51 | unsub() 52 | i++ 53 | } 54 | }) 55 | } 56 | 57 | func BenchmarkPublishingParallel(b *testing.B) { 58 | b.StopTimer() 59 | p := pubsub.New() 60 | for i := 0; i < 100; i++ { 61 | _, f := newSpySubscrption() 62 | p.Subscribe(f, pubsub.WithPath(randPath())) 63 | } 64 | b.StartTimer() 65 | 66 | b.RunParallel(func(b *testing.PB) { 67 | i := rand.Int() //nolint:gosec 68 | for b.Next() { 69 | p.Publish("data", pubsub.LinearTreeTraverser(randPath())) 70 | i++ 71 | } 72 | }) 73 | } 74 | 75 | func BenchmarkPublishingParallelStructs(b *testing.B) { 76 | b.StopTimer() 77 | p := pubsub.New() 78 | for i := 0; i < 100; i++ { 79 | _, f := newSpySubscrption() 80 | p.Subscribe(f, pubsub.WithPath(randPath())) 81 | } 82 | data := randStructs() 83 | st := StructTraverser{} 84 | b.StartTimer() 85 | 86 | b.RunParallel(func(b *testing.PB) { 87 | i := rand.Int() //nolint:gosec 88 | for b.Next() { 89 | p.Publish(data[i%len(data)], st.traverse) 90 | i++ 91 | } 92 | }) 93 | } 94 | 95 | func BenchmarkPublishingWhileSubscribing(b *testing.B) { 96 | b.StopTimer() 97 | p := pubsub.New() 98 | 99 | var wg sync.WaitGroup 100 | for x := 0; x < 5; x++ { 101 | wg.Add(1) 102 | go func() { 103 | wg.Done() 104 | for i := 0; ; i++ { 105 | _, f := newSpySubscrption() 106 | unsub := p.Subscribe(f, pubsub.WithPath(randPath())) 107 | if i%2 == 0 { 108 | unsub() 109 | } 110 | } 111 | }() 112 | } 113 | 114 | wg.Wait() 115 | b.StartTimer() 116 | 117 | b.RunParallel(func(b *testing.PB) { 118 | i := rand.Int() //nolint:gosec 119 | for b.Next() { 120 | p.Publish("data", pubsub.LinearTreeTraverser(randPath())) 121 | i++ 122 | } 123 | }) 124 | } 125 | 126 | func BenchmarkPublishingWhileSubscribingStructs(b *testing.B) { 127 | b.StopTimer() 128 | p := pubsub.New() 129 | data := randStructs() 130 | 131 | var wg sync.WaitGroup 132 | for x := 0; x < 5; x++ { 133 | wg.Add(1) 134 | go func() { 135 | wg.Done() 136 | for i := 0; ; i++ { 137 | _, f := newSpySubscrption() 138 | unsub := p.Subscribe(f, pubsub.WithPath(randPath())) 139 | if i%2 == 0 { 140 | unsub() 141 | } 142 | } 143 | }() 144 | } 145 | 146 | wg.Wait() 147 | st := StructTraverser{} 148 | b.StartTimer() 149 | 150 | b.RunParallel(func(b *testing.PB) { 151 | i := rand.Int() //nolint:gosec 152 | for b.Next() { 153 | p.Publish(data[i%len(data)], st.traverse) 154 | i++ 155 | } 156 | }) 157 | } 158 | 159 | func randPath() []uint64 { 160 | var r []uint64 161 | for i := 0; i < 10; i++ { 162 | r = append(r, uint64(rand.Int63n(10))) //nolint:gosec 163 | } 164 | return r 165 | } 166 | 167 | type someType struct { 168 | a uint64 169 | b uint64 170 | w *w 171 | x *x 172 | } 173 | 174 | type w struct { 175 | i uint64 176 | j uint64 177 | } 178 | 179 | type x struct { 180 | i uint64 181 | j uint64 182 | } 183 | 184 | func randNum(i int64) uint64 { 185 | return uint64(rand.Int63n(i)) //nolint:gosec 186 | } 187 | 188 | func randStructs() []*someType { 189 | var r []*someType 190 | for i := 0; i < 100000; i++ { 191 | r = append(r, &someType{ 192 | a: randNum(10), 193 | b: randNum(10), 194 | x: &x{ 195 | i: randNum(10), 196 | j: randNum(10), 197 | }, 198 | }) 199 | } 200 | return r 201 | } 202 | 203 | type StructTraverser struct{} 204 | 205 | func (s StructTraverser) traverse(data interface{}) pubsub.Paths { 206 | // a 207 | return pubsub.PathsWithTraverser([]uint64{0, data.(*someType).a}, pubsub.TreeTraverser(s.b)) 208 | } 209 | 210 | func (s StructTraverser) b(data interface{}) pubsub.Paths { 211 | return pubsub.PathAndTraversers( 212 | []pubsub.PathAndTraverser{ 213 | { 214 | Path: 0, 215 | Traverser: pubsub.TreeTraverser(s.w), 216 | }, 217 | { 218 | Path: data.(*someType).b, 219 | Traverser: pubsub.TreeTraverser(s.w), 220 | }, 221 | { 222 | Path: 0, 223 | Traverser: pubsub.TreeTraverser(s.x), 224 | }, 225 | { 226 | Path: data.(*someType).b, 227 | Traverser: pubsub.TreeTraverser(s.x), 228 | }, 229 | }, 230 | ) 231 | } 232 | 233 | var ( 234 | W = uint64(1) 235 | X = uint64(2) 236 | ) 237 | 238 | func (s StructTraverser) w(data interface{}) pubsub.Paths { 239 | if data.(*someType).w == nil { 240 | return pubsub.PathsWithTraverser([]uint64{0}, pubsub.TreeTraverser(s.done)) 241 | } 242 | 243 | return pubsub.PathsWithTraverser([]uint64{W}, pubsub.TreeTraverser(s.wi)) 244 | } 245 | 246 | func (s StructTraverser) wi(data interface{}) pubsub.Paths { 247 | return pubsub.PathsWithTraverser([]uint64{0, data.(*someType).w.i}, pubsub.TreeTraverser(s.wj)) 248 | } 249 | 250 | func (s StructTraverser) wj(data interface{}) pubsub.Paths { 251 | return pubsub.PathsWithTraverser([]uint64{0, data.(*someType).w.j}, pubsub.TreeTraverser(s.done)) 252 | } 253 | 254 | func (s StructTraverser) x(data interface{}) pubsub.Paths { 255 | if data.(*someType).x == nil { 256 | return pubsub.PathsWithTraverser([]uint64{0}, pubsub.TreeTraverser(s.done)) 257 | } 258 | 259 | return pubsub.PathsWithTraverser([]uint64{X}, pubsub.TreeTraverser(s.xi)) 260 | } 261 | 262 | func (s StructTraverser) xi(data interface{}) pubsub.Paths { 263 | return pubsub.PathsWithTraverser([]uint64{0, data.(*someType).x.i}, pubsub.TreeTraverser(s.xj)) 264 | } 265 | 266 | func (s StructTraverser) xj(data interface{}) pubsub.Paths { 267 | return pubsub.PathsWithTraverser([]uint64{0, data.(*someType).x.j}, pubsub.TreeTraverser(s.done)) 268 | } 269 | 270 | func (s StructTraverser) done(data interface{}) pubsub.Paths { 271 | return pubsub.FlatPaths(nil) 272 | } 273 | -------------------------------------------------------------------------------- /examples/structs/gen_struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "code.cloudfoundry.org/go-pubsub" 5 | "hash/crc64" 6 | ) 7 | 8 | func StructTravTraverse(data interface{}) pubsub.Paths { 9 | return _a(data) 10 | } 11 | 12 | func done(data interface{}) pubsub.Paths { 13 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 14 | return 0, nil, false 15 | }) 16 | } 17 | 18 | func hashBool(data bool) uint64 { 19 | // 0 is reserved 20 | if data { 21 | return 2 22 | } 23 | return 1 24 | } 25 | 26 | func hashUint64(data uint64) uint64 { 27 | // 0 is reserved 28 | if data == 0 { 29 | return 1 30 | } 31 | 32 | return data 33 | } 34 | 35 | var tableECMA = crc64.MakeTable(crc64.ECMA) 36 | 37 | func _a(data interface{}) pubsub.Paths { 38 | 39 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 40 | switch idx { 41 | case 0: 42 | return 0, pubsub.TreeTraverser(_b), true 43 | case 1: 44 | 45 | return hashUint64(crc64.Checksum([]byte(data.(*someType).a), tableECMA)), pubsub.TreeTraverser(_b), true 46 | default: 47 | return 0, nil, false 48 | } 49 | }) 50 | } 51 | 52 | func _b(data interface{}) pubsub.Paths { 53 | 54 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 55 | switch idx { 56 | case 0: 57 | return 0, 58 | pubsub.TreeTraverser(func(data interface{}) pubsub.Paths { 59 | return ___w_x 60 | }), true 61 | case 1: 62 | 63 | return hashUint64(crc64.Checksum([]byte(data.(*someType).b), tableECMA)), 64 | pubsub.TreeTraverser(func(data interface{}) pubsub.Paths { 65 | return ___w_x 66 | }), true 67 | default: 68 | return 0, nil, false 69 | } 70 | }) 71 | } 72 | 73 | func ___w_x(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 74 | switch idx { 75 | 76 | case 0: 77 | 78 | if data.(*someType).w == nil { 79 | return 0, pubsub.TreeTraverser(done), true 80 | } 81 | 82 | return 1, pubsub.TreeTraverser(_w_i), true 83 | 84 | case 1: 85 | 86 | if data.(*someType).x == nil { 87 | return 0, pubsub.TreeTraverser(done), true 88 | } 89 | 90 | return 2, pubsub.TreeTraverser(_x_i), true 91 | 92 | default: 93 | return 0, nil, false 94 | } 95 | } 96 | 97 | func _w(data interface{}) pubsub.Paths { 98 | 99 | if data.(*someType).w == nil { 100 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 101 | switch idx { 102 | case 0: 103 | return 0, pubsub.TreeTraverser(done), true 104 | default: 105 | return 0, nil, false 106 | } 107 | }) 108 | } 109 | 110 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 111 | switch idx { 112 | case 0: 113 | return 1, pubsub.TreeTraverser(_w_i), true 114 | default: 115 | return 0, nil, false 116 | } 117 | }) 118 | } 119 | 120 | func _w_i(data interface{}) pubsub.Paths { 121 | 122 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 123 | switch idx { 124 | case 0: 125 | return 0, pubsub.TreeTraverser(_w_j), true 126 | case 1: 127 | 128 | return hashUint64(crc64.Checksum([]byte(data.(*someType).w.i), tableECMA)), pubsub.TreeTraverser(_w_j), true 129 | default: 130 | return 0, nil, false 131 | } 132 | }) 133 | } 134 | 135 | func _w_j(data interface{}) pubsub.Paths { 136 | 137 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 138 | switch idx { 139 | case 0: 140 | return 0, pubsub.TreeTraverser(done), true 141 | case 1: 142 | 143 | return hashUint64(crc64.Checksum([]byte(data.(*someType).w.j), tableECMA)), pubsub.TreeTraverser(done), true 144 | default: 145 | return 0, nil, false 146 | } 147 | }) 148 | } 149 | 150 | func _x(data interface{}) pubsub.Paths { 151 | 152 | if data.(*someType).x == nil { 153 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 154 | switch idx { 155 | case 0: 156 | return 0, pubsub.TreeTraverser(done), true 157 | default: 158 | return 0, nil, false 159 | } 160 | }) 161 | } 162 | 163 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 164 | switch idx { 165 | case 0: 166 | return 1, pubsub.TreeTraverser(_x_i), true 167 | default: 168 | return 0, nil, false 169 | } 170 | }) 171 | } 172 | 173 | func _x_i(data interface{}) pubsub.Paths { 174 | 175 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 176 | switch idx { 177 | case 0: 178 | return 0, pubsub.TreeTraverser(_x_j), true 179 | case 1: 180 | 181 | return hashUint64(crc64.Checksum([]byte(data.(*someType).x.i), tableECMA)), pubsub.TreeTraverser(_x_j), true 182 | default: 183 | return 0, nil, false 184 | } 185 | }) 186 | } 187 | 188 | func _x_j(data interface{}) pubsub.Paths { 189 | 190 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 191 | switch idx { 192 | case 0: 193 | return 0, pubsub.TreeTraverser(done), true 194 | case 1: 195 | 196 | return hashUint64(crc64.Checksum([]byte(data.(*someType).x.j), tableECMA)), pubsub.TreeTraverser(done), true 197 | default: 198 | return 0, nil, false 199 | } 200 | }) 201 | } 202 | 203 | type someTypeFilter struct { 204 | a *string 205 | b *string 206 | w *wFilter 207 | x *xFilter 208 | } 209 | 210 | type wFilter struct { 211 | i *string 212 | j *string 213 | } 214 | 215 | type xFilter struct { 216 | i *string 217 | j *string 218 | } 219 | 220 | func StructTravCreatePath(f *someTypeFilter) []uint64 { 221 | if f == nil { 222 | return nil 223 | } 224 | var path []uint64 225 | 226 | var count int 227 | if f.w != nil { 228 | count++ 229 | } 230 | 231 | if f.x != nil { 232 | count++ 233 | } 234 | 235 | if count > 1 { 236 | panic("Only one field can be set") 237 | } 238 | 239 | if f.a != nil { 240 | 241 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.a), tableECMA))) 242 | } else { 243 | path = append(path, 0) 244 | } 245 | 246 | if f.b != nil { 247 | 248 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.b), tableECMA))) 249 | } else { 250 | path = append(path, 0) 251 | } 252 | 253 | path = append(path, createPath__w(f.w)...) 254 | 255 | path = append(path, createPath__x(f.x)...) 256 | 257 | for i := len(path) - 1; i >= 1; i-- { 258 | if path[i] != 0 { 259 | break 260 | } 261 | path = path[:i] 262 | } 263 | 264 | return path 265 | } 266 | 267 | func createPath__w(f *wFilter) []uint64 { 268 | if f == nil { 269 | return nil 270 | } 271 | var path []uint64 272 | 273 | path = append(path, 1) 274 | 275 | var count int 276 | if count > 1 { 277 | panic("Only one field can be set") 278 | } 279 | 280 | if f.i != nil { 281 | 282 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.i), tableECMA))) 283 | } else { 284 | path = append(path, 0) 285 | } 286 | 287 | if f.j != nil { 288 | 289 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.j), tableECMA))) 290 | } else { 291 | path = append(path, 0) 292 | } 293 | 294 | return path 295 | } 296 | 297 | func createPath__x(f *xFilter) []uint64 { 298 | if f == nil { 299 | return nil 300 | } 301 | var path []uint64 302 | 303 | path = append(path, 2) 304 | 305 | var count int 306 | if count > 1 { 307 | panic("Only one field can be set") 308 | } 309 | 310 | if f.i != nil { 311 | 312 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.i), tableECMA))) 313 | } else { 314 | path = append(path, 0) 315 | } 316 | 317 | if f.j != nil { 318 | 319 | path = append(path, hashUint64(crc64.Checksum([]byte(*f.j), tableECMA))) 320 | } else { 321 | path = append(path, 0) 322 | } 323 | 324 | return path 325 | } 326 | -------------------------------------------------------------------------------- /examples/structs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "code.cloudfoundry.org/go-pubsub" 8 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/setters" 9 | ) 10 | 11 | //go:generate $GOPATH/bin/pubsub-gen --output=$GOPATH/src/code.cloudfoundry.org/go-pubsub/examples/structs/gen_struct.go --pointer --struct-name=code.cloudfoundry.org/go-pubsub/examples/structs.someType --traverser=StructTrav --package=main 12 | 13 | type someType struct { 14 | a string 15 | b string 16 | w *w 17 | x *x 18 | } 19 | 20 | type w struct { 21 | i string 22 | j string 23 | } 24 | 25 | type x struct { 26 | i string 27 | j string 28 | } 29 | 30 | // Here we demonstrate how powerful a TreeTraverser can be. We define a 31 | // StructTraverser that reads each field. Fields can be left blank upon 32 | // subscription meaning the field is optinal. 33 | func main() { 34 | ps := pubsub.New() 35 | 36 | ps.Subscribe(Subscription("sub-0"), pubsub.WithPath(StructTravCreatePath(&someTypeFilter{ 37 | a: setters.String("a"), 38 | b: setters.String("b"), 39 | w: &wFilter{ 40 | i: setters.String("w.i"), 41 | j: setters.String("w.j"), 42 | }, 43 | }))) 44 | 45 | ps.Subscribe(Subscription("sub-1"), pubsub.WithPath(StructTravCreatePath(&someTypeFilter{ 46 | a: setters.String("a"), 47 | b: setters.String("b"), 48 | x: &xFilter{ 49 | i: setters.String("x.i"), 50 | j: setters.String("x.j"), 51 | }, 52 | }))) 53 | 54 | ps.Subscribe(Subscription("sub-2"), pubsub.WithPath(StructTravCreatePath(&someTypeFilter{ 55 | b: setters.String("b"), 56 | x: &xFilter{ 57 | i: setters.String("x.i"), 58 | j: setters.String("x.j"), 59 | }, 60 | }))) 61 | 62 | ps.Subscribe(Subscription("sub-3"), pubsub.WithPath(StructTravCreatePath(&someTypeFilter{ 63 | x: &xFilter{ 64 | i: setters.String("x.i"), 65 | j: setters.String("x.j"), 66 | }, 67 | }))) 68 | 69 | ps.Subscribe(Subscription("sub-4")) 70 | 71 | ps.Publish(&someType{a: "a", b: "b", w: &w{i: "w.i", j: "w.j"}, x: &x{i: "x.i", j: "x.j"}}, StructTravTraverse) 72 | ps.Publish(&someType{a: "a", b: "b", x: &x{i: "x.i", j: "x.j"}}, StructTravTraverse) 73 | ps.Publish(&someType{a: "a'", b: "b'", x: &x{i: "x.i", j: "x.j"}}, StructTravTraverse) 74 | ps.Publish(&someType{a: "a", b: "b"}, StructTravTraverse) 75 | } 76 | 77 | // Subscription writes any results to stderr 78 | func Subscription(s string) func(interface{}) { 79 | return func(data interface{}) { 80 | d := data.(*someType) 81 | var w string 82 | if d.w != nil { 83 | w = fmt.Sprintf("w:{i:%s j:%s}", d.w.i, d.w.j) 84 | } 85 | 86 | var x string 87 | if d.x != nil { 88 | x = fmt.Sprintf("x:{i:%s j:%s}", d.x.i, d.x.j) 89 | } 90 | log.Printf("%s <- {a:%s b:%s %s %s}", s, d.a, d.b, w, x) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /gen_struct_test.go: -------------------------------------------------------------------------------- 1 | package pubsub_test 2 | 3 | import ( 4 | "code.cloudfoundry.org/go-pubsub" 5 | "hash/crc64" 6 | ) 7 | 8 | func testStructTravTraverse(data interface{}) pubsub.Paths { 9 | return _a(data) 10 | } 11 | 12 | func done(data interface{}) pubsub.Paths { 13 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 14 | return 0, nil, false 15 | }) 16 | } 17 | 18 | func hashBool(data bool) uint64 { 19 | // 0 is reserved 20 | if data { 21 | return 2 22 | } 23 | return 1 24 | } 25 | 26 | func hashUint64(data uint64) uint64 { 27 | // 0 is reserved 28 | if data == 0 { 29 | return 1 30 | } 31 | 32 | return data 33 | } 34 | 35 | var tableECMA = crc64.MakeTable(crc64.ECMA) 36 | 37 | func _a(data interface{}) pubsub.Paths { 38 | 39 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 40 | switch idx { 41 | case 0: 42 | return 0, pubsub.TreeTraverser(_b), true 43 | case 1: 44 | 45 | return hashUint64(uint64(data.(*testStruct).a)), pubsub.TreeTraverser(_b), true 46 | default: 47 | return 0, nil, false 48 | } 49 | }) 50 | } 51 | 52 | func _b(data interface{}) pubsub.Paths { 53 | 54 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 55 | switch idx { 56 | case 0: 57 | return 0, 58 | pubsub.TreeTraverser(func(data interface{}) pubsub.Paths { 59 | return ___aa_bb 60 | }), true 61 | case 1: 62 | 63 | return hashUint64(uint64(data.(*testStruct).b)), 64 | pubsub.TreeTraverser(func(data interface{}) pubsub.Paths { 65 | return ___aa_bb 66 | }), true 67 | default: 68 | return 0, nil, false 69 | } 70 | }) 71 | } 72 | 73 | func ___aa_bb(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 74 | switch idx { 75 | 76 | case 0: 77 | 78 | if data.(*testStruct).aa == nil { 79 | return 0, pubsub.TreeTraverser(done), true 80 | } 81 | 82 | return 1, pubsub.TreeTraverser(_aa_a), true 83 | 84 | case 1: 85 | 86 | if data.(*testStruct).bb == nil { 87 | return 0, pubsub.TreeTraverser(done), true 88 | } 89 | 90 | return 2, pubsub.TreeTraverser(_bb_b), true 91 | 92 | default: 93 | return 0, nil, false 94 | } 95 | } 96 | 97 | func _aa(data interface{}) pubsub.Paths { 98 | 99 | if data.(*testStruct).aa == nil { 100 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 101 | switch idx { 102 | case 0: 103 | return 0, pubsub.TreeTraverser(done), true 104 | default: 105 | return 0, nil, false 106 | } 107 | }) 108 | } 109 | 110 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 111 | switch idx { 112 | case 0: 113 | return 1, pubsub.TreeTraverser(_aa_a), true 114 | default: 115 | return 0, nil, false 116 | } 117 | }) 118 | } 119 | 120 | func _aa_a(data interface{}) pubsub.Paths { 121 | 122 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 123 | switch idx { 124 | case 0: 125 | return 0, pubsub.TreeTraverser(done), true 126 | case 1: 127 | 128 | return hashUint64(uint64(data.(*testStruct).aa.a)), pubsub.TreeTraverser(done), true 129 | default: 130 | return 0, nil, false 131 | } 132 | }) 133 | } 134 | 135 | func _bb(data interface{}) pubsub.Paths { 136 | 137 | if data.(*testStruct).bb == nil { 138 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 139 | switch idx { 140 | case 0: 141 | return 0, pubsub.TreeTraverser(done), true 142 | default: 143 | return 0, nil, false 144 | } 145 | }) 146 | } 147 | 148 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 149 | switch idx { 150 | case 0: 151 | return 1, pubsub.TreeTraverser(_bb_b), true 152 | default: 153 | return 0, nil, false 154 | } 155 | }) 156 | } 157 | 158 | func _bb_b(data interface{}) pubsub.Paths { 159 | 160 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool) { 161 | switch idx { 162 | case 0: 163 | return 0, pubsub.TreeTraverser(done), true 164 | case 1: 165 | 166 | return hashUint64(uint64(data.(*testStruct).bb.b)), pubsub.TreeTraverser(done), true 167 | default: 168 | return 0, nil, false 169 | } 170 | }) 171 | } 172 | 173 | type testStructFilter struct { 174 | a *int 175 | b *int 176 | aa *testStructAFilter 177 | bb *testStructBFilter 178 | } 179 | 180 | type testStructAFilter struct { 181 | a *int 182 | } 183 | 184 | type testStructBFilter struct { 185 | b *int 186 | } 187 | 188 | func testStructTravCreatePath(f *testStructFilter) []uint64 { 189 | if f == nil { 190 | return nil 191 | } 192 | var path []uint64 193 | 194 | var count int 195 | if f.aa != nil { 196 | count++ 197 | } 198 | 199 | if f.bb != nil { 200 | count++ 201 | } 202 | 203 | if count > 1 { 204 | panic("Only one field can be set") 205 | } 206 | 207 | if f.a != nil { 208 | 209 | path = append(path, hashUint64(uint64(*f.a))) 210 | } else { 211 | path = append(path, 0) 212 | } 213 | 214 | if f.b != nil { 215 | 216 | path = append(path, hashUint64(uint64(*f.b))) 217 | } else { 218 | path = append(path, 0) 219 | } 220 | 221 | path = append(path, createPath__aa(f.aa)...) 222 | 223 | path = append(path, createPath__bb(f.bb)...) 224 | 225 | for i := len(path) - 1; i >= 1; i-- { 226 | if path[i] != 0 { 227 | break 228 | } 229 | path = path[:i] 230 | } 231 | 232 | return path 233 | } 234 | 235 | func createPath__aa(f *testStructAFilter) []uint64 { 236 | if f == nil { 237 | return nil 238 | } 239 | var path []uint64 240 | 241 | path = append(path, 1) 242 | 243 | var count int 244 | if count > 1 { 245 | panic("Only one field can be set") 246 | } 247 | 248 | if f.a != nil { 249 | 250 | path = append(path, hashUint64(uint64(*f.a))) 251 | } else { 252 | path = append(path, 0) 253 | } 254 | 255 | return path 256 | } 257 | 258 | func createPath__bb(f *testStructBFilter) []uint64 { 259 | if f == nil { 260 | return nil 261 | } 262 | var path []uint64 263 | 264 | path = append(path, 2) 265 | 266 | var count int 267 | if count > 1 { 268 | panic("Only one field can be set") 269 | } 270 | 271 | if f.b != nil { 272 | 273 | path = append(path, hashUint64(uint64(*f.b))) 274 | } else { 275 | path = append(path, 0) 276 | } 277 | 278 | return path 279 | } 280 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module code.cloudfoundry.org/go-pubsub 2 | 3 | go 1.17 4 | 5 | require github.com/poy/onpar v1.1.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/a8m/expect v1.0.0/go.mod h1:4IwSCMumY49ScypDnjNbYEjgVeqy1/U2cEs3Lat96eA= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 8 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 9 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 10 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 11 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 12 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 13 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 14 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 15 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 16 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 17 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 20 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 21 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 22 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 23 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 24 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 25 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 26 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 27 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 28 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 29 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 30 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 31 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 32 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 33 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 34 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 35 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 36 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 37 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 38 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 39 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 40 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 41 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 42 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 43 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 44 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 45 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 46 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 47 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 48 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 49 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 50 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 54 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 55 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 56 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 57 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 58 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 59 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 60 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 61 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 62 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 63 | github.com/nelsam/hel/v2 v2.3.2 h1:tXRsJBqRxj4ISSPCrXhbqF8sT+BXA/UaIvjhYjP5Bhk= 64 | github.com/nelsam/hel/v2 v2.3.2/go.mod h1:1ZTGfU2PFTOd5mx22i5O0Lc2GY933lQ2wb/ggy+rL3w= 65 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 66 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 67 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 68 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 69 | github.com/poy/onpar v0.0.0-20200406201722-06f95a1c68e8/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= 70 | github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= 71 | github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= 72 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 73 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 74 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 75 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 76 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 77 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 78 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 79 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 80 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 81 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 82 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 83 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 84 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 85 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 86 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 87 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 88 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 89 | github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 90 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 91 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 92 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 93 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 94 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 95 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 96 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 97 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 98 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 99 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 100 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 101 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 102 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 103 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 104 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 105 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 106 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 107 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 108 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 109 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 110 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 111 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 112 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 113 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 114 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 115 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 116 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 117 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 118 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 119 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 120 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 122 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 125 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 128 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 129 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 130 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 132 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 133 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 134 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 135 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 136 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 137 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 138 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 139 | golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 140 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 141 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 142 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 143 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 144 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 145 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 146 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 147 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 148 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 149 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 150 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 151 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 152 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 153 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 154 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 155 | -------------------------------------------------------------------------------- /internal/node/node.go: -------------------------------------------------------------------------------- 1 | package node 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | type Node struct { 8 | children map[uint64]*Node 9 | subscriptions map[string]subscriptionInfo 10 | shards map[int64]string 11 | rand func(int64) int64 12 | } 13 | 14 | type subscriptionInfo struct { 15 | deterministicRoutingCount int 16 | envelopes []SubscriptionEnvelope 17 | } 18 | 19 | type SubscriptionEnvelope struct { 20 | Subscription func(interface{}) 21 | id int64 22 | dName string 23 | } 24 | 25 | func New(int63n func(n int64) int64) *Node { 26 | return &Node{ 27 | children: make(map[uint64]*Node), 28 | subscriptions: make(map[string]subscriptionInfo), 29 | shards: make(map[int64]string), 30 | rand: int63n, 31 | } 32 | } 33 | 34 | func (n *Node) AddChild(key uint64) *Node { 35 | if n == nil { 36 | return nil 37 | } 38 | 39 | if child, ok := n.children[key]; ok { 40 | return child 41 | } 42 | 43 | child := New(n.rand) 44 | n.children[key] = child 45 | return child 46 | } 47 | 48 | func (n *Node) FetchChild(key uint64) *Node { 49 | if n == nil { 50 | return nil 51 | } 52 | 53 | if child, ok := n.children[key]; ok { 54 | return child 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func (n *Node) DeleteChild(key uint64) { 61 | if n == nil { 62 | return 63 | } 64 | 65 | delete(n.children, key) 66 | } 67 | 68 | func (n *Node) ChildLen() int { 69 | return len(n.children) 70 | } 71 | 72 | func (n *Node) AddSubscription(s func(interface{}), shardID, deterministicRoutingName string) int64 { 73 | if n == nil { 74 | return 0 75 | } 76 | 77 | id := n.createAndSetID(shardID) 78 | 79 | si := n.subscriptions[shardID] 80 | si.envelopes = append(si.envelopes, SubscriptionEnvelope{ 81 | Subscription: s, 82 | id: id, 83 | dName: deterministicRoutingName, 84 | }) 85 | if deterministicRoutingName != "" { 86 | si.deterministicRoutingCount++ 87 | } 88 | 89 | sort.Sort(envelopes(si.envelopes)) 90 | n.subscriptions[shardID] = si 91 | 92 | return id 93 | } 94 | 95 | func (n *Node) DeleteSubscription(id int64) { 96 | if n == nil { 97 | return 98 | } 99 | 100 | shardID, ok := n.shards[id] 101 | if !ok { 102 | return 103 | } 104 | 105 | delete(n.shards, id) 106 | 107 | s := n.subscriptions[shardID] 108 | for i, ss := range s.envelopes { 109 | if ss.id != id { 110 | continue 111 | } 112 | 113 | if ss.dName != "" { 114 | s.deterministicRoutingCount-- 115 | } 116 | 117 | s.envelopes = append(s.envelopes[:i], s.envelopes[i+1:]...) 118 | break 119 | } 120 | 121 | n.subscriptions[shardID] = s 122 | 123 | if len(n.subscriptions[shardID].envelopes) == 0 { 124 | delete(n.subscriptions, shardID) 125 | } 126 | } 127 | 128 | func (n *Node) SubscriptionLen() int { 129 | return len(n.shards) 130 | } 131 | 132 | func (n *Node) ForEachSubscription(f func(shardID string, isDeterministic bool, s []SubscriptionEnvelope)) { 133 | if n == nil { 134 | return 135 | } 136 | 137 | for shardID, s := range n.subscriptions { 138 | f(shardID, s.deterministicRoutingCount > 0, s.envelopes) 139 | } 140 | } 141 | 142 | func (n *Node) createAndSetID(shardID string) int64 { 143 | id := n.rand(0x7FFFFFFFFFFFFFFF) 144 | for { 145 | if _, ok := n.shards[id]; ok { 146 | id++ 147 | continue 148 | } 149 | n.shards[id] = shardID 150 | return id 151 | } 152 | } 153 | 154 | type envelopes []SubscriptionEnvelope 155 | 156 | func (e envelopes) Len() int { 157 | return len(e) 158 | } 159 | 160 | func (e envelopes) Less(a, b int) bool { 161 | return e[a].dName < e[b].dName 162 | } 163 | 164 | func (e envelopes) Swap(a, b int) { 165 | tmp := e[a] 166 | e[a] = e[b] 167 | e[b] = tmp 168 | } 169 | -------------------------------------------------------------------------------- /internal/node/node_test.go: -------------------------------------------------------------------------------- 1 | package node_test 2 | 3 | import ( 4 | "math/rand" 5 | "sort" 6 | "testing" 7 | 8 | "code.cloudfoundry.org/go-pubsub/internal/node" 9 | "github.com/poy/onpar" 10 | . "github.com/poy/onpar/expect" 11 | . "github.com/poy/onpar/matchers" 12 | ) 13 | 14 | type TN struct { 15 | *testing.T 16 | n *node.Node 17 | } 18 | 19 | func TestNode(t *testing.T) { 20 | t.Parallel() 21 | o := onpar.New() 22 | defer o.Run(t) 23 | 24 | o.BeforeEach(func(t *testing.T) TN { 25 | return TN{ 26 | T: t, 27 | n: node.New(rand.Int63n), 28 | } 29 | }) 30 | 31 | o.Spec("returns nil for nil node", func(t TN) { 32 | var nilNode *node.Node 33 | n := nilNode.FetchChild(99) 34 | Expect(t, n == nil).To(BeTrue()) 35 | }) 36 | 37 | o.Spec("returns nil for unknown child", func(t TN) { 38 | n := t.n.FetchChild(99) 39 | Expect(t, n == nil).To(BeTrue()) 40 | }) 41 | 42 | o.Spec("returns child", func(t TN) { 43 | n1 := t.n.AddChild(1) 44 | n2 := t.n.FetchChild(1) 45 | Expect(t, n1).To(Equal(n2)) 46 | Expect(t, t.n.ChildLen()).To(Equal(1)) 47 | 48 | // Removes child upon deletion 49 | t.n.DeleteChild(1) 50 | Expect(t, t.n.FetchChild(1) == nil).To(BeTrue()) 51 | }) 52 | 53 | o.Spec("returns all subscriptions", func(t TN) { 54 | id1 := t.n.AddSubscription(func(interface{}) {}, "", "") 55 | 56 | t.n.AddSubscription(func(interface{}) {}, "", "") 57 | t.n.AddSubscription(func(interface{}) {}, "", "") 58 | t.n.DeleteSubscription(id1) 59 | 60 | var ss []func(interface{}) 61 | t.n.ForEachSubscription(func(id string, isD bool, s []node.SubscriptionEnvelope) { 62 | for _, x := range s { 63 | ss = append(ss, x.Subscription) 64 | } 65 | Expect(t, isD).To(Equal(false)) 66 | }) 67 | Expect(t, ss).To(HaveLen(2)) 68 | Expect(t, t.n.SubscriptionLen()).To(Equal(2)) 69 | }) 70 | 71 | o.Spec("returns is deterministic if a single route has deterministic name", func(t TN) { 72 | t.n.AddSubscription(func(interface{}) {}, "a", "") 73 | t.n.AddSubscription(func(interface{}) {}, "a", "some-name") 74 | 75 | t.n.ForEachSubscription(func(id string, isD bool, s []node.SubscriptionEnvelope) { 76 | Expect(t, isD).To(Equal(true)) 77 | }) 78 | }) 79 | 80 | o.Spec("returns is not deterministic if all deterministic names have been deleted", func(t TN) { 81 | t.n.AddSubscription(func(interface{}) {}, "a", "") 82 | id := t.n.AddSubscription(func(interface{}) {}, "a", "some-name") 83 | t.n.DeleteSubscription(id) 84 | 85 | t.n.ForEachSubscription(func(id string, isD bool, s []node.SubscriptionEnvelope) { 86 | Expect(t, isD).To(Equal(false)) 87 | }) 88 | }) 89 | 90 | o.Spec("returns subscriptions in order of deterministic routing name", func(t TN) { 91 | var track []int 92 | t.n.AddSubscription(func(interface{}) { track = append(track, 2) }, "a", "2") 93 | t.n.AddSubscription(func(interface{}) { track = append(track, 1) }, "a", "1") 94 | 95 | t.n.ForEachSubscription(func(id string, isD bool, s []node.SubscriptionEnvelope) { 96 | for _, x := range s { 97 | x.Subscription(nil) 98 | } 99 | }) 100 | 101 | Expect(t, sort.IntsAreSorted(track)).To(Equal(true)) 102 | }) 103 | 104 | o.Spec("it handles ID collisions", func(t TN) { 105 | n := node.New(func(int64) int64 { return 0 }) 106 | id1 := n.AddSubscription(func(interface{}) {}, "", "") 107 | id2 := n.AddSubscription(func(interface{}) {}, "", "") 108 | 109 | Expect(t, id1).To(Not(Equal(id2))) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /pubsub-gen/internal/end2end/end2end_test.go: -------------------------------------------------------------------------------- 1 | package end2end_test 2 | 3 | //go:generate ./generate.sh 4 | 5 | import ( 6 | "flag" 7 | "testing" 8 | 9 | "code.cloudfoundry.org/go-pubsub" 10 | . "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/end2end" 11 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/setters" 12 | "github.com/poy/onpar" 13 | . "github.com/poy/onpar/expect" 14 | . "github.com/poy/onpar/matchers" 15 | ) 16 | 17 | func TestEnd2End(t *testing.T) { 18 | t.Parallel() 19 | o := onpar.New() 20 | defer o.Run(t) 21 | flag.Parse() 22 | 23 | o.Spec("routes data as expected", func(t *testing.T) { 24 | ps := pubsub.New() 25 | sub1 := &mockSubscription{} 26 | sub2 := &mockSubscription{} 27 | sub3 := &mockSubscription{} 28 | sub4 := &mockSubscription{} 29 | sub5 := &mockSubscription{} 30 | sub6 := &mockSubscription{} 31 | sub7 := &mockSubscription{} 32 | sub8 := &mockSubscription{} 33 | sub9 := &mockSubscription{} 34 | sub10 := &mockSubscription{} 35 | 36 | ps.Subscribe(sub1.write, pubsub.WithPath(StructTraverserCreatePath(nil))) 37 | ps.Subscribe(sub2.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{ 38 | I: setters.Int(1), 39 | Y1: &YFilter{ 40 | I: setters.Int(1), 41 | J: setters.String("a"), 42 | }, 43 | }))) 44 | ps.Subscribe(sub3.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{ 45 | Y1: &YFilter{ 46 | J: setters.String("b"), 47 | }, 48 | }))) 49 | ps.Subscribe(sub4.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{ 50 | Y2: &YFilter{}, 51 | }))) 52 | ps.Subscribe(sub5.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{ 53 | M_M2: &M2Filter{ 54 | B: setters.Int(2), 55 | }, 56 | }))) 57 | 58 | ps.Subscribe(sub6.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{Repeated: []string{"a", "b", "c"}}))) 59 | ps.Subscribe(sub7.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{RepeatedY: nil}))) 60 | ps.Subscribe(sub8.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{MapY: []string{"a", "b"}}))) 61 | ps.Subscribe(sub9.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{E2: &EmptyFilter{}}))) 62 | ps.Subscribe(sub10.write, pubsub.WithPath(StructTraverserCreatePath(&XFilter{ 63 | M_M3: &M3Filter{ 64 | A: &M1Filter{ 65 | A: setters.Int(9), 66 | }, 67 | }, 68 | }))) 69 | 70 | ps.Publish(&X{ 71 | I: 1, 72 | J: "a", 73 | E2: &Empty{}, 74 | Repeated: []string{"a", "b", "c"}, 75 | RepeatedY: []Y{{I: 99, J: "a"}, {I: 99, J: "b"}, {I: 99, J: "c"}}, 76 | Y1: Y{I: 1, J: "a"}, Y2: &Y{I: 1, J: "a"}, 77 | MapY: map[string]Y{"a": {}, "b": {}}, 78 | }, 79 | StructTraverserTraverse, 80 | ) 81 | ps.Publish(&X{I: 1, J: "a", Y1: Y{I: 2, J: "b"}, Y2: &Y{I: 1, J: "a"}}, StructTraverserTraverse) 82 | ps.Publish(&X{I: 1, J: "x", Y1: Y{I: 2, J: "b"}}, StructTraverserTraverse) 83 | ps.Publish(&X{I: 1, J: "x", Y1: Y{I: 2, J: "b"}, M: &M2{A: 1, B: 2}}, StructTraverserTraverse) 84 | ps.Publish(&X{M: &M3{A: M1{A: 9}}}, StructTraverserTraverse) 85 | 86 | Expect(t, sub1.callCount).To(Equal(5)) 87 | Expect(t, sub2.callCount).To(Equal(1)) 88 | Expect(t, sub3.callCount).To(Equal(3)) 89 | Expect(t, sub4.callCount).To(Equal(2)) 90 | Expect(t, sub5.callCount).To(Equal(1)) 91 | Expect(t, sub6.callCount).To(Equal(1)) 92 | Expect(t, sub7.callCount).To(Equal(5)) 93 | Expect(t, sub8.callCount).To(Equal(1)) 94 | Expect(t, sub9.callCount).To(Equal(1)) 95 | Expect(t, sub10.callCount).To(Equal(1)) 96 | }) 97 | } 98 | 99 | type mockSubscription struct { 100 | callCount int 101 | } 102 | 103 | func (m *mockSubscription) write(data interface{}) { 104 | m.callCount++ 105 | } 106 | -------------------------------------------------------------------------------- /pubsub-gen/internal/end2end/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go install code.cloudfoundry.org/go-pubsub/pubsub-gen 4 | 5 | $GOPATH/bin/pubsub-gen \ 6 | --struct-name=code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/end2end.X \ 7 | --package=end2end_test \ 8 | --traverser=StructTraverser \ 9 | --output=$GOPATH/src/code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/end2end/generated_traverser_test.go \ 10 | --pointer \ 11 | --interfaces='{"message":["M1","*M2","*M3"]}' \ 12 | --include-pkg-name=true \ 13 | --imports='{"code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/end2end":""}' \ 14 | --slices='{"X.RepeatedY":"I","RepeatedEmpty":""}' 15 | 16 | gofmt -s -w . 17 | -------------------------------------------------------------------------------- /pubsub-gen/internal/end2end/test_types.go: -------------------------------------------------------------------------------- 1 | package end2end 2 | 3 | type X struct { 4 | I int 5 | J string 6 | Y1 Y 7 | Y2 *Y 8 | E1 Empty 9 | E2 *Empty 10 | M message 11 | Repeated []string 12 | RepeatedY []Y 13 | RepeatedEmpty []Empty 14 | MapY map[string]Y 15 | } 16 | 17 | type Y struct { 18 | I int 19 | J string 20 | E1 Empty 21 | E2 *Empty 22 | } 23 | 24 | type Empty struct{} 25 | 26 | type message interface { 27 | message() 28 | } 29 | 30 | type M1 struct { 31 | A int 32 | } 33 | 34 | func (m M1) message() {} 35 | 36 | type M2 struct { 37 | A int 38 | B int 39 | } 40 | 41 | func (m *M2) message() {} 42 | 43 | type M3 struct { 44 | A M1 45 | //I int TODO 46 | } 47 | 48 | func (m *M3) message() {} 49 | -------------------------------------------------------------------------------- /pubsub-gen/internal/generator/code_writer.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 9 | ) 10 | 11 | type CodeWriter struct{} 12 | 13 | func (w CodeWriter) Package(name string) string { 14 | return fmt.Sprintf("package %s\n\n", name) 15 | } 16 | 17 | func (w CodeWriter) Imports(names map[string]string) string { 18 | result := "import (\n" 19 | for n, x := range names { 20 | if n == "" { 21 | continue 22 | } 23 | if x == "" { 24 | result += fmt.Sprintf(" \"%s\"\n", n) 25 | continue 26 | } 27 | 28 | result += fmt.Sprintf(" %s \"%s\"\n", x, n) 29 | } 30 | return fmt.Sprintf("%s)\n", result) 31 | } 32 | 33 | func (w CodeWriter) Traverse(travName, firstField string) string { 34 | return fmt.Sprintf(` 35 | func %sTraverse(data interface{}) pubsub.Paths { 36 | return _%s(data) 37 | } 38 | `, travName, firstField) 39 | } 40 | 41 | func (w CodeWriter) Done(travName string) string { 42 | return ` 43 | func done(data interface{}) pubsub.Paths { 44 | return pubsub.Paths( func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 45 | return 0, nil, false 46 | }) 47 | } 48 | ` 49 | } 50 | 51 | func (w CodeWriter) Hashers(travName string) string { 52 | return ` 53 | func hashBool(data bool) uint64 { 54 | // 0 is reserved 55 | if data { 56 | return 2 57 | } 58 | return 1 59 | } 60 | 61 | func hashUint64(data uint64) uint64 { 62 | // 0 is reserved 63 | if data == 0{ 64 | return 1 65 | } 66 | 67 | return data 68 | } 69 | 70 | var tableECMA = crc64.MakeTable(crc64.ECMA) 71 | ` 72 | 73 | } 74 | 75 | func (w CodeWriter) FieldStartStruct(travName, prefix, fieldName, parentFieldName, castTypeName string, isPtr bool, enumValue int) string { 76 | var nilCheck string 77 | if isPtr { 78 | nilCheck = fmt.Sprintf(` 79 | if %s == nil { 80 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 81 | switch idx { 82 | case 0: 83 | return 0, pubsub.TreeTraverser(done), true 84 | default: 85 | return 0, nil, false 86 | } 87 | }) 88 | } 89 | `, castTypeName) 90 | } 91 | 92 | // Remove any * that may have been added 93 | prefix = strings.ReplaceAll(prefix, "*", "") 94 | 95 | if fieldName == "" { 96 | return fmt.Sprintf(` 97 | func %s(data interface{}) pubsub.Paths { 98 | %s 99 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 100 | switch idx { 101 | case 0: 102 | return %d, pubsub.TreeTraverser(done), true 103 | default: 104 | return 0, nil, false 105 | } 106 | }) 107 | } 108 | `, prefix, nilCheck, enumValue) 109 | } 110 | 111 | return fmt.Sprintf(` 112 | func %s(data interface{}) pubsub.Paths { 113 | %s 114 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 115 | switch idx { 116 | case 0: 117 | return %d, pubsub.TreeTraverser(%s_%s), true 118 | default: 119 | return 0, nil, false 120 | } 121 | }) 122 | } 123 | `, prefix, nilCheck, enumValue, prefix, fieldName) 124 | } 125 | 126 | func (w CodeWriter) FieldSelector(travName, prefix, fieldName, parentFieldName, castTypeName string, isPtr bool, enumValue int) string { 127 | var nilCheck string 128 | if isPtr { 129 | nilCheck = fmt.Sprintf(` 130 | if %s.%s == nil { 131 | return 0, pubsub.TreeTraverser(done), true 132 | } 133 | `, castTypeName, parentFieldName) 134 | } 135 | 136 | if fieldName == "" { 137 | return fmt.Sprintf(` 138 | %s 139 | // Empty field name (%s.%s) 140 | return %d, pubsub.TreeTraverser(done), true 141 | `, nilCheck, castTypeName, parentFieldName, enumValue) 142 | 143 | } 144 | 145 | prefix = strings.ReplaceAll(prefix, "*", "") 146 | return fmt.Sprintf(` 147 | %s 148 | return %d, pubsub.TreeTraverser(%s_%s), true 149 | `, nilCheck, enumValue, prefix, fieldName) 150 | } 151 | 152 | func (w CodeWriter) SelectorFunc(travName, prefix, selectorName string, fields []string) string { 153 | var body string 154 | for i, f := range fields { 155 | body += fmt.Sprintf(` 156 | case %d: 157 | %s 158 | `, i, f) 159 | } 160 | 161 | // Remove any * that may have been added 162 | prefix = strings.ReplaceAll(prefix, "*", "") 163 | 164 | return fmt.Sprintf(` 165 | func __%s_%s (idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 166 | switch idx{ 167 | %s 168 | default: 169 | return 0, nil, false 170 | } 171 | } 172 | `, prefix, selectorName, body) 173 | } 174 | 175 | func (w CodeWriter) FieldStructFunc(travName, prefix, fieldName, nextFieldName, castTypeName, hashType string, isPtr bool, slice inspector.Slice, m inspector.Map) string { 176 | var nilCheck string 177 | if isPtr || slice.IsSlice { 178 | 179 | nilCheck = fmt.Sprintf(` 180 | if %s.%s == nil { 181 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 182 | switch idx { 183 | case 0: 184 | return 0, pubsub.TreeTraverser(%s_%s), true 185 | default: 186 | return 0, nil, false 187 | } 188 | }) 189 | } 190 | `, castTypeName, fieldName, prefix, nextFieldName) 191 | } 192 | 193 | var star string 194 | if isPtr { 195 | star = "*" 196 | } 197 | 198 | dataValue := fmt.Sprintf("%s%s.%s", star, castTypeName, fieldName) 199 | hashCalc, hashValue := hashSplitFn(hashType, dataValue, slice, m) 200 | 201 | // Remove any * that may have been added 202 | prefix = strings.ReplaceAll(prefix, "*", "") 203 | 204 | return fmt.Sprintf(` 205 | func %s_%s(data interface{}) pubsub.Paths { 206 | %s 207 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 208 | switch idx { 209 | case 0: 210 | return 0, pubsub.TreeTraverser(%s_%s), true 211 | case 1: 212 | %s 213 | return %s, pubsub.TreeTraverser(%s_%s), true 214 | default: 215 | return 0, nil, false 216 | } 217 | }) 218 | } 219 | `, prefix, fieldName, nilCheck, prefix, nextFieldName, hashCalc, hashValue, prefix, nextFieldName) 220 | } 221 | 222 | func (w CodeWriter) FieldStructFuncLast(travName, prefix, fieldName, castTypeName, hashType string, isPtr bool, slice inspector.Slice, m inspector.Map) string { 223 | var nilCheck string 224 | if isPtr || slice.IsSlice { 225 | nilCheck = fmt.Sprintf(` 226 | if %s.%s == nil { 227 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 228 | switch idx { 229 | case 0: 230 | return 0, pubsub.TreeTraverser(done), true 231 | default: 232 | return 0, nil, false 233 | } 234 | }) 235 | } 236 | `, castTypeName, fieldName) 237 | } 238 | 239 | var star string 240 | if isPtr { 241 | star = "*" 242 | } 243 | 244 | dataValue := fmt.Sprintf("%s%s.%s", star, castTypeName, fieldName) 245 | hashCalc, hashValue := hashSplitFn(hashType, dataValue, slice, m) 246 | 247 | // Remove any * that may have been added 248 | prefix = strings.ReplaceAll(prefix, "*", "") 249 | 250 | return fmt.Sprintf(` 251 | func %s_%s(data interface{}) pubsub.Paths { 252 | %s 253 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 254 | switch idx { 255 | case 0: 256 | return 0, pubsub.TreeTraverser(done), true 257 | case 1: 258 | %s 259 | return %s, pubsub.TreeTraverser(done), true 260 | default: 261 | return 0, nil, false 262 | } 263 | }) 264 | } 265 | `, prefix, fieldName, nilCheck, hashCalc, hashValue) 266 | } 267 | 268 | func (w CodeWriter) FieldPeersFunc(travName, prefix, castTypeName, fieldName, hashType string, names []string, isPtr bool, slice inspector.Slice, m inspector.Map) string { 269 | travFunc := fmt.Sprintf(` 270 | pubsub.TreeTraverser(func(data interface{}) pubsub.Paths { 271 | return __%s_%s 272 | })`, prefix, strings.Join(names, "_")) 273 | 274 | var nilCheck string 275 | if isPtr || slice.IsSlice { 276 | nilCheck = fmt.Sprintf(` 277 | if %s.%s == nil { 278 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 279 | switch idx { 280 | case 0: 281 | return 0, %s, true 282 | default: 283 | return 0, nil, false 284 | } 285 | }) 286 | } 287 | `, castTypeName, fieldName, travFunc) 288 | } 289 | 290 | var star string 291 | if isPtr { 292 | star = "*" 293 | } 294 | 295 | dataValue := fmt.Sprintf("%s%s.%s", star, castTypeName, fieldName) 296 | hashCalc, hashValue := hashSplitFn(hashType, dataValue, slice, m) 297 | 298 | // Remove any * that may have been added 299 | prefix = strings.ReplaceAll(prefix, "*", "") 300 | 301 | return fmt.Sprintf(` 302 | func %s_%s(data interface{}) pubsub.Paths { 303 | %s 304 | return pubsub.Paths(func(idx int, data interface{}) (path uint64, nextTraverser pubsub.TreeTraverser, ok bool){ 305 | switch idx{ 306 | case 0: 307 | return 0, %s, true 308 | case 1: 309 | %s 310 | return %s, %s, true 311 | default: 312 | return 0, nil, false 313 | } 314 | }) 315 | } 316 | `, prefix, fieldName, nilCheck, travFunc, hashCalc, hashValue, travFunc) 317 | } 318 | 319 | func (w CodeWriter) InterfaceSelector(prefix, castTypeName, fieldName, structPkgPrefix string, implementers map[string]string, startIdx int) string { 320 | idxs := orderImpls(implementers) 321 | body := fmt.Sprintf("switch %s.%s.(type) {", castTypeName, fieldName) 322 | for i, f := range implementers { 323 | var star string 324 | if strings.HasPrefix(i, "*") { 325 | star = "*" 326 | } 327 | i = strings.Trim(i, "*") 328 | 329 | if f == "" { 330 | body += fmt.Sprintf(` 331 | case %s%s%s: 332 | // Interface implementation with no fields 333 | return %d, pubsub.TreeTraverser(done), true 334 | `, star, structPkgPrefix, i, idxs[i]+startIdx) 335 | continue 336 | } 337 | 338 | body += fmt.Sprintf(` 339 | case %s%s%s: 340 | return %d, %s_%s_%s_%s, true 341 | `, star, structPkgPrefix, i, idxs[i]+startIdx, prefix, fieldName, i, f) 342 | } 343 | body += ` 344 | default: 345 | return 0, pubsub.TreeTraverser(done), true 346 | }` 347 | 348 | return body 349 | } 350 | 351 | func orderImpls(impls map[string]string) map[string]int { 352 | m := make(map[string]int) 353 | 354 | var names []string 355 | for k := range impls { 356 | names = append(names, strings.Trim(k, "*")) 357 | } 358 | 359 | sort.Strings(names) 360 | 361 | for i, s := range names { 362 | m[s] = i + 1 363 | } 364 | 365 | return m 366 | } 367 | -------------------------------------------------------------------------------- /pubsub-gen/internal/generator/path_generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 9 | ) 10 | 11 | type PathGenerator struct { 12 | } 13 | 14 | func NewPathGenerator() PathGenerator { 15 | return PathGenerator{} 16 | } 17 | 18 | func (g PathGenerator) Generate( 19 | existingSrc string, 20 | m map[string]inspector.Struct, 21 | genName string, 22 | structName string, 23 | ) (string, error) { 24 | src, err := g.genStruct(existingSrc, m, structName, make(map[string]bool)) 25 | if err != nil { 26 | return "", err 27 | } 28 | 29 | src, err = g.genPath(src, "", m, genName, structName, genName+"CreatePath", true, 0) 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | return src, err 35 | } 36 | 37 | func (g PathGenerator) genPath( 38 | src string, 39 | prefix string, 40 | m map[string]inspector.Struct, 41 | genName string, 42 | structName string, 43 | funcName string, 44 | includeMinimize bool, 45 | enumValue int, 46 | ) (string, error) { 47 | body, err := g.genPathBody( 48 | m, 49 | structName, 50 | ) 51 | 52 | if err != nil { 53 | return "", err 54 | } 55 | 56 | structName = strings.Trim(structName, "*") 57 | s, ok := m[structName] 58 | if !ok { 59 | return "", fmt.Errorf("unknown struct %s", structName) 60 | } 61 | 62 | var next string 63 | for _, pf := range s.PeerTypeFields { 64 | next += g.genPathNextFunc(m, prefix, pf.Name) 65 | } 66 | 67 | for f, implementers := range s.InterfaceTypeFields { 68 | ii := make([]string, len(implementers)) 69 | copy(ii, implementers) 70 | 71 | for i, v := range ii { 72 | ii[i] = strings.Trim(v, "*") 73 | } 74 | 75 | sort.Strings(ii) 76 | 77 | for _, i := range ii { 78 | next += g.genPathNextFunc(m, prefix, fmt.Sprintf("%s_%s", f.Name, i)) 79 | } 80 | } 81 | 82 | var addLabel string 83 | if enumValue != 0 { 84 | addLabel = fmt.Sprintf(`path = append(path, %d)`, enumValue) 85 | } 86 | 87 | var minimize string 88 | if includeMinimize { 89 | minimize = ` 90 | for i := len(path) - 1; i >= 1; i-- { 91 | if path[i] != 0 { 92 | break 93 | } 94 | path = path[:i] 95 | } 96 | ` 97 | } 98 | 99 | src += fmt.Sprintf(` 100 | func %s(f *%sFilter) []uint64 { 101 | if f == nil { 102 | return nil 103 | } 104 | var path []uint64 105 | 106 | %s 107 | 108 | %s 109 | 110 | %s 111 | 112 | %s 113 | 114 | return path 115 | } 116 | `, funcName, g.sanitizeName(structName), addLabel, body, next, minimize) 117 | 118 | var idx int 119 | for _, pf := range s.PeerTypeFields { 120 | src, _ = g.genPath(src, fmt.Sprintf("%s_%s", prefix, pf.Name), m, genName, pf.Type, fmt.Sprintf("createPath_%s_%s", prefix, pf.Name), false, idx+1) 121 | idx++ 122 | } 123 | 124 | for f, implementers := range s.InterfaceTypeFields { 125 | ii := make([]string, len(implementers)) 126 | copy(ii, implementers) 127 | 128 | for i, v := range ii { 129 | ii[i] = strings.Trim(v, "*") 130 | } 131 | 132 | sort.Strings(ii) 133 | for j, i := range ii { 134 | 135 | i = strings.ReplaceAll(i, "*", "") 136 | 137 | src, err = g.genPath(src, fmt.Sprintf("%s_%s", prefix, i), m, genName, i, fmt.Sprintf("createPath_%s_%s_%s", prefix, f.Name, i), false, j+idx+1) 138 | if err != nil { 139 | return "", err 140 | } 141 | } 142 | } 143 | 144 | return src, nil 145 | } 146 | 147 | func (g PathGenerator) sanitizeName(name string) string { 148 | return strings.ReplaceAll(name, ".", "") 149 | } 150 | 151 | func (g PathGenerator) genPathNextFunc( 152 | m map[string]inspector.Struct, 153 | prefix string, 154 | structName string, 155 | ) string { 156 | structName = strings.ReplaceAll(structName, "*", "") 157 | return fmt.Sprintf(` 158 | path = append(path, createPath_%s_%s(f.%s)...) 159 | `, prefix, structName, structName) 160 | } 161 | 162 | func (g PathGenerator) genPathBody( 163 | m map[string]inspector.Struct, 164 | structName string, 165 | ) (string, error) { 166 | var src string 167 | 168 | structName = strings.Trim(structName, "*") 169 | s, ok := m[structName] 170 | if !ok { 171 | return "", fmt.Errorf("unknown struct %s", structName) 172 | } 173 | 174 | onlyOneCheck := "var count int" 175 | for _, f := range s.PeerTypeFields { 176 | onlyOneCheck += fmt.Sprintf(` 177 | if f.%s != nil{ 178 | count++ 179 | } 180 | `, f.Name) 181 | } 182 | 183 | for f, implementers := range s.InterfaceTypeFields { 184 | for _, i := range implementers { 185 | onlyOneCheck += fmt.Sprintf(` 186 | if f.%s_%s != nil{ 187 | count++ 188 | } 189 | `, f.Name, strings.Trim(i, "*")) 190 | } 191 | } 192 | 193 | onlyOneCheck += ` 194 | if count > 1 { 195 | panic("Only one field can be set") 196 | } 197 | ` 198 | 199 | buildPath := "" 200 | for _, f := range s.Fields { 201 | var star string 202 | if !f.Slice.IsSlice && !f.Map.IsMap { 203 | star = "*" 204 | } 205 | 206 | // We convert maps to slices for the path. This allows users to enter the 207 | // desired keys more easily. 208 | if f.Map.IsMap { 209 | f.Map.IsMap = false 210 | f.Slice.IsSlice = true 211 | } 212 | 213 | dataValue := fmt.Sprintf("%sf.%s", star, f.Name) 214 | f.Slice.IsBasicType = true 215 | hashCalc, hashValue := hashSplitFn(f.Type, dataValue, f.Slice, inspector.Map{}) 216 | 217 | buildPath += fmt.Sprintf(` 218 | if f.%s != nil { 219 | %s 220 | path = append(path, %s) 221 | }else{ 222 | path = append(path, 0) 223 | } 224 | `, f.Name, hashCalc, hashValue) 225 | } 226 | 227 | src += fmt.Sprintf(` 228 | %s 229 | %s 230 | `, onlyOneCheck, buildPath) 231 | 232 | return src, nil 233 | } 234 | 235 | func (g PathGenerator) genStruct( 236 | src string, 237 | m map[string]inspector.Struct, 238 | structName string, 239 | history map[string]bool, 240 | ) (string, error) { 241 | if history[structName] { 242 | return src, nil 243 | } 244 | history[structName] = true 245 | 246 | structName = strings.Trim(structName, "*") 247 | s, ok := m[structName] 248 | if !ok { 249 | return "", fmt.Errorf("unknown struct %s", structName) 250 | } 251 | 252 | var fields string 253 | for _, f := range s.Fields { 254 | if f.Slice.IsSlice || f.Map.IsMap { 255 | fields += fmt.Sprintf("%s []%s\n", f.Name, f.Type) 256 | continue 257 | } 258 | 259 | fields += fmt.Sprintf("%s *%s\n", f.Name, f.Type) 260 | } 261 | 262 | for _, f := range s.PeerTypeFields { 263 | fields += fmt.Sprintf("%s *%sFilter\n", f.Name, g.sanitizeName(f.Type)) 264 | } 265 | 266 | for f, implementers := range s.InterfaceTypeFields { 267 | for _, i := range implementers { 268 | i = strings.Trim(i, "*") 269 | fields += fmt.Sprintf("%s_%s *%sFilter\n", f.Name, i, i) 270 | } 271 | } 272 | 273 | src += fmt.Sprintf(` 274 | type %sFilter struct{ 275 | %s 276 | } 277 | `, g.sanitizeName(structName), fields) 278 | 279 | for _, f := range s.PeerTypeFields { 280 | var err error 281 | src, err = g.genStruct(src, m, f.Type, history) 282 | if err != nil { 283 | return "", err 284 | } 285 | } 286 | 287 | for _, implementers := range s.InterfaceTypeFields { 288 | for _, i := range implementers { 289 | var err error 290 | src, err = g.genStruct(src, m, i, history) 291 | if err != nil { 292 | return "", err 293 | } 294 | } 295 | } 296 | 297 | return src, nil 298 | } 299 | -------------------------------------------------------------------------------- /pubsub-gen/internal/generator/traverser_generator.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 8 | ) 9 | 10 | type TraverserWriter interface { 11 | Package(name string) string 12 | Imports(names map[string]string) string 13 | Done(travName string) string 14 | Traverse(travName, name string) string 15 | Hashers(travName string) string 16 | 17 | FieldSelector(travName, prefix, fieldName, parentFieldName, castTypeName string, isPtr bool, enumValue int) string 18 | InterfaceSelector(prefix, castTypeName, fieldName, structPkgPrefix string, implementers map[string]string, startIdx int) string 19 | SelectorFunc(travName, prefix, selectorName string, fields []string) string 20 | 21 | FieldStartStruct(travName, prefix, fieldName, parentFieldName, castTypeName string, isPtr bool, enumValue int) string 22 | FieldStructFunc(travName, prefix, fieldName, nextFieldName, castTypeName, hashFn string, isPtr bool, slice inspector.Slice, m inspector.Map) string 23 | FieldStructFuncLast(travName, prefix, fieldName, castTypeName, hashFn string, isPtr bool, slice inspector.Slice, m inspector.Map) string 24 | FieldPeersFunc(travName, prefix, castTypeName, fieldName, hashFn string, names []string, isPtr bool, slice inspector.Slice, m inspector.Map) string 25 | } 26 | 27 | type TraverserGenerator struct { 28 | writer TraverserWriter 29 | } 30 | 31 | func NewTraverserGenerator(w TraverserWriter) TraverserGenerator { 32 | return TraverserGenerator{ 33 | writer: w, 34 | } 35 | } 36 | 37 | func (g TraverserGenerator) Generate( 38 | m map[string]inspector.Struct, 39 | packageName string, 40 | traverserName string, 41 | structName string, 42 | isPtr bool, 43 | structPkgPrefix string, 44 | imports map[string]string, 45 | ) (string, error) { 46 | src := g.writer.Package(packageName) 47 | 48 | imports["code.cloudfoundry.org/go-pubsub"] = "" 49 | imports["hash/crc64"] = "" 50 | 51 | src += g.writer.Imports(imports) 52 | 53 | structName = strings.Trim(structName, "*") 54 | s, ok := m[structName] 55 | if !ok { 56 | return "", fmt.Errorf("unknown struct %s", structName) 57 | } 58 | 59 | var name string 60 | if len(s.Fields) > 0 { 61 | name = s.Fields[0].Name 62 | } 63 | 64 | src += g.writer.Traverse(traverserName, name) 65 | src += g.writer.Done(traverserName) 66 | src += g.writer.Hashers(traverserName) 67 | 68 | var ptr string 69 | if isPtr { 70 | ptr = "*" 71 | } 72 | 73 | return g.generateStructFns( 74 | src, 75 | structName, 76 | traverserName, 77 | "", 78 | "", 79 | fmt.Sprintf("data.(%s%s%s)", ptr, structPkgPrefix, structName), 80 | false, 81 | structPkgPrefix, 82 | m, 83 | ) 84 | } 85 | 86 | func (g TraverserGenerator) generateStructFns( 87 | src string, 88 | structName string, 89 | traverserName string, 90 | prefix string, 91 | parentFieldName string, 92 | castTypeName string, 93 | isPtr bool, 94 | structPkgPrefix string, 95 | m map[string]inspector.Struct, 96 | ) (string, error) { 97 | structName = strings.Trim(structName, "*") 98 | s, ok := m[structName] 99 | if !ok { 100 | return "", fmt.Errorf("unknown struct %s", structName) 101 | } 102 | 103 | if parentFieldName != "" { 104 | var name string 105 | if len(s.Fields) > 0 { 106 | name = s.Fields[0].Name 107 | } 108 | 109 | src += g.writer.FieldStartStruct( 110 | traverserName, 111 | prefix, 112 | name, 113 | parentFieldName, 114 | castTypeName, 115 | isPtr, 116 | 1, 117 | ) 118 | } 119 | 120 | if len(s.Fields) > 0 { 121 | for i, f := range s.Fields[:len(s.Fields)-1] { 122 | src += g.writer.FieldStructFunc( 123 | traverserName, 124 | prefix, 125 | f.Name, 126 | s.Fields[i+1].Name, 127 | castTypeName, 128 | f.Type, 129 | f.Ptr, 130 | f.Slice, 131 | f.Map, 132 | ) 133 | } 134 | 135 | if len(s.PeerTypeFields) == 0 && len(s.InterfaceTypeFields) == 0 { 136 | return src + g.writer.FieldStructFuncLast( 137 | traverserName, 138 | prefix, 139 | s.Fields[len(s.Fields)-1].Name, 140 | castTypeName, 141 | s.Fields[len(s.Fields)-1].Type, 142 | s.Fields[len(s.Fields)-1].Ptr, 143 | s.Fields[len(s.Fields)-1].Slice, 144 | s.Fields[len(s.Fields)-1].Map, 145 | ), nil 146 | } 147 | } 148 | 149 | var peerFields []string 150 | var fieldNames []string 151 | 152 | // Struct Peers 153 | var i int 154 | for _, f := range s.PeerTypeFields { 155 | x, ok := m[f.Type] 156 | if !ok { 157 | continue 158 | } 159 | 160 | var name string 161 | if len(x.Fields) > 0 { 162 | name = x.Fields[0].Name 163 | } 164 | 165 | fieldNames = append(fieldNames, f.Name) 166 | peerFields = append(peerFields, g.writer.FieldSelector( 167 | traverserName, 168 | fmt.Sprintf("%s_%s", prefix, f.Name), 169 | name, 170 | f.Name, 171 | castTypeName, 172 | f.Ptr, 173 | i+1, 174 | )) 175 | i++ 176 | } 177 | 178 | // Interface Peers 179 | for field, implementers := range s.InterfaceTypeFields { 180 | implementersWithFields := make(map[string]string) 181 | for _, impl := range implementers { 182 | trimmedImpl := strings.Trim(impl, "*") 183 | if len(m[trimmedImpl].Fields) == 0 && len(m[trimmedImpl].PeerTypeFields) == 0 { 184 | implementersWithFields[impl] = "" 185 | continue 186 | } 187 | 188 | var name string 189 | if len(m[trimmedImpl].PeerTypeFields) > 0 { 190 | name = m[trimmedImpl].PeerTypeFields[0].Name 191 | } else if len(m[trimmedImpl].Fields) > 0 { 192 | name = m[trimmedImpl].Fields[0].Name 193 | } 194 | 195 | implementersWithFields[impl] = name 196 | } 197 | 198 | fieldNames = append(fieldNames, field.Name) 199 | peerFields = append(peerFields, g.writer.InterfaceSelector( 200 | prefix, 201 | castTypeName, 202 | field.Name, 203 | 204 | structPkgPrefix, 205 | implementersWithFields, 206 | i, 207 | )) 208 | } 209 | 210 | if len(s.Fields) > 0 { 211 | src += g.writer.FieldPeersFunc( 212 | traverserName, 213 | prefix, 214 | castTypeName, 215 | s.Fields[len(s.Fields)-1].Name, 216 | s.Fields[len(s.Fields)-1].Type, 217 | fieldNames, 218 | s.Fields[len(s.Fields)-1].Ptr, 219 | s.Fields[len(s.Fields)-1].Slice, 220 | s.Fields[len(s.Fields)-1].Map, 221 | ) 222 | } 223 | 224 | if len(peerFields) != 0 { 225 | src += g.writer.SelectorFunc(traverserName, prefix, strings.Join(fieldNames, "_"), peerFields) 226 | } 227 | 228 | for _, field := range s.PeerTypeFields { 229 | var err error 230 | src, err = g.generateStructFns( 231 | src, 232 | field.Type, 233 | traverserName, 234 | fmt.Sprintf("%s_%s", prefix, field.Name), 235 | field.Name, 236 | fmt.Sprintf("%s.%s", castTypeName, field.Name), 237 | field.Ptr, 238 | structPkgPrefix, 239 | m, 240 | ) 241 | if err != nil { 242 | return "", err 243 | } 244 | } 245 | 246 | for field, implementers := range s.InterfaceTypeFields { 247 | for _, i := range implementers { 248 | var name string 249 | if strings.HasPrefix(i, "*") { 250 | name = fmt.Sprintf("%s.%s.(%s%s)", castTypeName, field.Name, "*"+structPkgPrefix, strings.Trim(i, "*")) 251 | } else { 252 | name = fmt.Sprintf("%s.%s.(%s%s)", castTypeName, field.Name, structPkgPrefix, i) 253 | } 254 | 255 | var err error 256 | src, err = g.generateStructFns( 257 | src, 258 | i, 259 | traverserName, 260 | fmt.Sprintf("%s_%s_%s", prefix, field.Name, i), 261 | i, 262 | name, 263 | false, 264 | structPkgPrefix, 265 | m, 266 | ) 267 | if err != nil { 268 | return "", err 269 | } 270 | } 271 | } 272 | 273 | return src, nil 274 | } 275 | 276 | func hashSplitFn(t, dataValue string, slice inspector.Slice, m inspector.Map) (calc, value string) { 277 | if slice.IsSlice { 278 | x := "x" 279 | if !slice.IsBasicType { 280 | x = fmt.Sprintf("x.%s", slice.FieldName) 281 | } 282 | 283 | _, value := hashSplitFn(t, x, inspector.Slice{}, inspector.Map{}) 284 | return fmt.Sprintf(` 285 | var total uint64 286 | for _, x := range %s{ 287 | total += %s 288 | }`, dataValue, value), "hashUint64(total)" 289 | } 290 | 291 | if m.IsMap { 292 | _, value := hashSplitFn(t, "x", inspector.Slice{}, inspector.Map{}) 293 | return fmt.Sprintf(` 294 | var total uint64 295 | for x := range %s{ 296 | total += %s 297 | }`, dataValue, value), "hashUint64(total)" 298 | } 299 | 300 | switch t { 301 | case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "byte", "uint16", "uint32", "uint64", "float32", "float64": 302 | return "", fmt.Sprintf("hashUint64(uint64(%s))", dataValue) 303 | case "string": 304 | return "", fmt.Sprintf("hashUint64(crc64.Checksum([]byte(%s), tableECMA))", dataValue) 305 | case "bool": 306 | return "", fmt.Sprintf("hashBool(%s)", dataValue) 307 | default: 308 | return "", dataValue 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/linker.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type Linker struct{} 8 | 9 | func NewLinker() Linker { 10 | return Linker{} 11 | } 12 | 13 | func (l Linker) Link(m map[string]Struct, interfaceToStruct map[string][]string) { 14 | for n := range m { 15 | l.linkFields(n, m, interfaceToStruct) 16 | l.linkSliceTypes(n, m) 17 | } 18 | } 19 | 20 | func (l Linker) linkFields(n string, m map[string]Struct, mi map[string][]string) { 21 | s := m[n] 22 | if s.InterfaceTypeFields == nil { 23 | s.InterfaceTypeFields = make(map[Field][]string) 24 | } 25 | 26 | for i, f := range s.Fields { 27 | _, ok := m[f.Type] 28 | if ok && !f.Slice.IsSlice { 29 | s.PeerTypeFields = append(s.PeerTypeFields, f) 30 | s.Fields = append(s.Fields[:i], s.Fields[i+1:]...) 31 | m[n] = s 32 | 33 | // We want to restart the loop because we've messed with our indexes 34 | l.linkFields(n, m, mi) 35 | return 36 | } 37 | 38 | t, ok := mi[f.Type] 39 | if ok { 40 | s.InterfaceTypeFields[f] = append(s.InterfaceTypeFields[f], t...) 41 | s.Fields = append(s.Fields[:i], s.Fields[i+1:]...) 42 | m[n] = s 43 | } 44 | } 45 | } 46 | 47 | func (l Linker) linkSliceTypes(n string, m map[string]Struct) { 48 | s := m[n] 49 | 50 | for i, f := range s.Fields { 51 | if !f.Slice.IsSlice || f.Slice.IsBasicType { 52 | continue 53 | } 54 | 55 | peer, ok := m[f.Type] 56 | if !ok { 57 | log.Fatalf("Unknown type for slice: %s", f.Type) 58 | } 59 | 60 | ff, ok := l.findFieldName(f.Slice.FieldName, peer.Fields) 61 | if !ok { 62 | log.Fatalf("Unknown field name for slice: %s %s", f.Type, f.Slice.FieldName) 63 | } 64 | 65 | f.Type = ff.Type 66 | s.Fields[i] = f 67 | } 68 | } 69 | 70 | func (l Linker) findFieldName(name string, fields []Field) (Field, bool) { 71 | for _, ff := range fields { 72 | if ff.Name == name { 73 | return ff, true 74 | } 75 | } 76 | return Field{}, false 77 | } 78 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/linker_test.go: -------------------------------------------------------------------------------- 1 | package inspector_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 7 | "github.com/poy/onpar" 8 | . "github.com/poy/onpar/expect" 9 | . "github.com/poy/onpar/matchers" 10 | ) 11 | 12 | type TL struct { 13 | *testing.T 14 | l inspector.Linker 15 | } 16 | 17 | func TestLinker(t *testing.T) { 18 | t.Parallel() 19 | o := onpar.New() 20 | defer o.Run(t) 21 | 22 | o.BeforeEach(func(t *testing.T) TL { 23 | return TL{ 24 | T: t, 25 | l: inspector.NewLinker(), 26 | } 27 | }) 28 | 29 | o.Spec("moves any known field types to PeerTypeFields", func(t TL) { 30 | m := map[string]inspector.Struct{ 31 | "X": {Fields: []inspector.Field{ 32 | {Name: "A", Type: "string"}, 33 | {Name: "B", Type: "Y"}, 34 | {Name: "C", Type: "Y"}, 35 | }}, 36 | "Y": {Fields: []inspector.Field{ 37 | {Name: "A", Type: "string"}, 38 | {Name: "B", Type: "int"}, 39 | }}, 40 | } 41 | t.l.Link(m, nil) 42 | 43 | Expect(t, m["X"].Fields).To(HaveLen(1)) 44 | Expect(t, m["X"].Fields[0].Name).To(Equal("A")) 45 | 46 | Expect(t, m["X"].PeerTypeFields).To(HaveLen(2)) 47 | Expect(t, m["X"].PeerTypeFields[0].Name).To(Equal("B")) 48 | Expect(t, m["X"].PeerTypeFields[1].Name).To(Equal("C")) 49 | }) 50 | 51 | o.Spec("moves any known interface types to InterfaceTypeFields", func(t TL) { 52 | m := map[string]inspector.Struct{ 53 | "X": {Fields: []inspector.Field{ 54 | {Name: "A", Type: "string"}, 55 | {Name: "B", Type: "Y"}, 56 | {Name: "C", Type: "Y"}, 57 | }}, 58 | "Y": {Fields: []inspector.Field{ 59 | {Name: "A", Type: "string"}, 60 | {Name: "B", Type: "int"}, 61 | {Name: "C", Type: "MyInterfaceThing"}, 62 | }}, 63 | } 64 | 65 | mi := map[string][]string{ 66 | "MyInterfaceThing": { 67 | "X", "Y", 68 | }, 69 | } 70 | c := m["Y"].Fields[2] 71 | 72 | t.l.Link(m, mi) 73 | Expect(t, m["Y"].Fields).To(HaveLen(2)) 74 | Expect(t, m["Y"].InterfaceTypeFields).To(HaveLen(1)) 75 | Expect(t, m["Y"].InterfaceTypeFields[c]).To(And( 76 | HaveLen(2), 77 | Contain("X", "Y"), 78 | )) 79 | }) 80 | 81 | o.Spec("adds non-basic slice types with given field type", func(t TL) { 82 | m := map[string]inspector.Struct{ 83 | "X": {Fields: []inspector.Field{ 84 | {Name: "A", Type: "Y", Slice: inspector.Slice{ 85 | IsSlice: true, 86 | IsBasicType: false, 87 | FieldName: "B", 88 | }}, 89 | {Name: "B", Type: "string", Slice: inspector.Slice{ 90 | IsSlice: true, 91 | IsBasicType: true, 92 | }}, 93 | }}, 94 | "Y": {Fields: []inspector.Field{ 95 | {Name: "A", Type: "string"}, 96 | {Name: "B", Type: "int"}, 97 | {Name: "C", Type: "MyInterfaceThing"}, 98 | }}, 99 | } 100 | 101 | t.l.Link(m, nil) 102 | 103 | Expect(t, m["X"].Fields).To(HaveLen(2)) 104 | Expect(t, m["X"].Fields[0].Name).To(Equal("A")) 105 | Expect(t, m["X"].Fields[0].Slice.IsSlice).To(BeTrue()) 106 | Expect(t, m["X"].Fields[0].Slice.IsBasicType).To(BeFalse()) 107 | Expect(t, m["X"].Fields[0].Slice.FieldName).To(Equal("B")) 108 | Expect(t, m["X"].Fields[0].Type).To(Equal("int")) // Comes from Y.B 109 | 110 | // Is left alone because it is a basic type 111 | Expect(t, m["X"].Fields[1].Name).To(Equal("B")) 112 | Expect(t, m["X"].Fields[1].Type).To(Equal("string")) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/package_parser.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | type StructParser interface { 13 | Parse(n ast.Node) ([]Struct, error) 14 | } 15 | 16 | type PackageParser struct { 17 | s StructParser 18 | } 19 | 20 | func NewPackageParser(s StructParser) PackageParser { 21 | return PackageParser{ 22 | s: s, 23 | } 24 | } 25 | 26 | func (p PackageParser) Parse(packagePath, gopath string) (map[string]Struct, error) { 27 | pkgPath := filepath.Join(gopath, "src", packagePath) 28 | files, err := os.ReadDir(pkgPath) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | m := make(map[string]Struct) 34 | for _, file := range files { 35 | if file.IsDir() || filepath.Ext(file.Name()) != ".go" { 36 | continue 37 | } 38 | 39 | filePath := filepath.Join(pkgPath, file.Name()) 40 | fileData, err := os.ReadFile(filePath) 41 | if err != nil { 42 | return nil, fmt.Errorf("%s -> %s", filePath, err) 43 | } 44 | 45 | fset := token.NewFileSet() 46 | n, err := parser.ParseFile(fset, filePath, fileData, 0) 47 | if err != nil { 48 | return nil, fmt.Errorf("%s -> %s", filePath, err) 49 | } 50 | 51 | ss, err := p.s.Parse(n) 52 | 53 | if err != nil { 54 | return nil, fmt.Errorf("%s -> %s", filePath, err) 55 | } 56 | 57 | for _, s := range ss { 58 | m[s.Name] = s 59 | } 60 | } 61 | return m, nil 62 | } 63 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/package_parser_test.go: -------------------------------------------------------------------------------- 1 | package inspector_test 2 | 3 | import ( 4 | "go/ast" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 10 | "github.com/poy/onpar" 11 | . "github.com/poy/onpar/expect" 12 | . "github.com/poy/onpar/matchers" 13 | ) 14 | 15 | type TPP struct { 16 | *testing.T 17 | structParser *spyStructParser 18 | p inspector.PackageParser 19 | gopath string 20 | } 21 | 22 | func TestStructPackageParser(t *testing.T) { 23 | t.Parallel() 24 | o := onpar.New() 25 | defer o.Run(t) 26 | 27 | o.BeforeEach(func(t *testing.T) TPP { 28 | gopath := writeTestPackage() 29 | structParser := &spyStructParser{returnValue: []inspector.Struct{ 30 | {Name: "X"}, {Name: "Y"}, {Name: "Z"}, 31 | }} 32 | return TPP{ 33 | T: t, 34 | structParser: structParser, 35 | p: inspector.NewPackageParser(structParser), 36 | gopath: gopath, 37 | } 38 | }) 39 | 40 | o.Spec("it opens each file in the given path", func(t TPP) { 41 | structs, err := t.p.Parse("some-package", t.gopath) 42 | Expect(t, err == nil).To(BeTrue()) 43 | Expect(t, t.structParser.nodes).To(HaveLen(2)) 44 | Expect(t, structs).To(HaveLen(3)) 45 | }) 46 | 47 | o.Spec("it returns an error for an unknown path", func(t TPP) { 48 | _, err := t.p.Parse("garbage-package", t.gopath) 49 | Expect(t, err == nil).To(BeFalse()) 50 | }) 51 | } 52 | 53 | func writeTestPackage() string { 54 | dir, err := os.MkdirTemp("", "ast-gen") 55 | if err != nil { 56 | panic(err) 57 | } 58 | os.Mkdir(filepath.Join(dir, "src"), os.ModePerm) //nolint:errcheck 59 | os.Mkdir(filepath.Join(dir, "src", "some-package"), os.ModePerm) //nolint:errcheck 60 | 61 | // nolint:gosec 62 | err = os.WriteFile(filepath.Join(dir, "src", "some-package", "test1.go"), 63 | []byte( 64 | ` 65 | package p 66 | `, 67 | ), 68 | os.ModePerm) 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | // nolint:gosec 74 | err = os.WriteFile(filepath.Join(dir, "src", "some-package", "test2.go"), 75 | []byte( 76 | ` 77 | package p 78 | `, 79 | ), 80 | os.ModePerm) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | return dir 86 | } 87 | 88 | type spyStructParser struct { 89 | nodes []ast.Node 90 | returnValue []inspector.Struct 91 | err error 92 | } 93 | 94 | func (s *spyStructParser) Parse(n ast.Node) ([]inspector.Struct, error) { 95 | s.nodes = append(s.nodes, n) 96 | return s.returnValue, s.err 97 | } 98 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/struct_fetcher.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | ) 7 | 8 | type Field struct { 9 | Name string 10 | Type string 11 | Ptr bool 12 | Slice Slice 13 | Map Map 14 | } 15 | 16 | type Slice struct { 17 | IsSlice bool 18 | IsBasicType bool 19 | FieldName string 20 | } 21 | 22 | type Map struct { 23 | IsMap bool 24 | } 25 | 26 | type Struct struct { 27 | Name string 28 | Fields []Field 29 | PeerTypeFields []Field 30 | InterfaceTypeFields map[Field][]string 31 | } 32 | 33 | type StructFetcher struct { 34 | blacklist map[string][]string 35 | knownTypes map[string]string 36 | sliceTypes map[string]string 37 | } 38 | 39 | func NewStructFetcher(blacklist map[string][]string, knownTypes, sliceTypes map[string]string) StructFetcher { 40 | return StructFetcher{ 41 | blacklist: blacklist, 42 | knownTypes: knownTypes, 43 | sliceTypes: sliceTypes, 44 | } 45 | } 46 | 47 | func (f StructFetcher) Parse(n ast.Node) ([]Struct, error) { 48 | var structs []Struct 49 | var name string 50 | 51 | ast.Inspect(n, func(n ast.Node) bool { 52 | switch x := n.(type) { 53 | case *ast.Ident: 54 | name = x.Name 55 | case *ast.StructType: 56 | if name == "" { 57 | return true 58 | } 59 | 60 | fields := f.extractFields(name, x.Fields) 61 | structs = append(structs, Struct{Name: name, Fields: fields}) 62 | name = "" 63 | } 64 | return true 65 | }) 66 | 67 | return structs, nil 68 | } 69 | 70 | func (f StructFetcher) extractFields(parentName string, n ast.Node) []Field { 71 | var fields []Field 72 | ast.Inspect(n, func(n ast.Node) bool { 73 | switch x := n.(type) { 74 | case *ast.Field: 75 | name, ptr, slice, isMap := f.extractType(x.Type) 76 | 77 | var basicSliceType bool 78 | var sliceFieldName string 79 | if slice { 80 | var ok bool 81 | ok, basicSliceType, sliceFieldName = f.isOKSliceType(name, parentName, f.firstName(x.Names)) 82 | if !ok { 83 | return true 84 | } 85 | } 86 | 87 | if isMap { 88 | if !f.isBasicType(name) { 89 | return true 90 | } 91 | } 92 | 93 | ff := Field{ 94 | Name: f.firstName(x.Names), 95 | Type: name, 96 | Ptr: ptr, 97 | Slice: Slice{ 98 | IsSlice: slice, 99 | IsBasicType: basicSliceType, 100 | FieldName: sliceFieldName, 101 | }, 102 | Map: Map{ 103 | IsMap: isMap, 104 | }, 105 | } 106 | 107 | if ff.Name != "" && ff.Type != "" && !f.inBlacklist(ff.Name, parentName) { 108 | fields = append(fields, ff) 109 | } 110 | } 111 | return true 112 | }) 113 | return fields 114 | } 115 | 116 | func (f StructFetcher) isBasicType(name string) bool { 117 | switch name { 118 | case 119 | "int", 120 | "int8", 121 | "int32", 122 | "int64", 123 | "uint", 124 | "uint8", 125 | "uint32", 126 | "uint64", 127 | "string", 128 | "float32", 129 | "float64", 130 | "bool", 131 | "byte": 132 | return true 133 | default: 134 | return false 135 | } 136 | } 137 | 138 | func (f StructFetcher) isOKSliceType(name, structName, fieldName string) (ok, basicType bool, getFieldName string) { 139 | if f.isBasicType(name) { 140 | return true, true, "" 141 | } 142 | 143 | fn, ok := f.sliceTypes[fmt.Sprintf("%s.%s", structName, fieldName)] 144 | return ok, false, fn 145 | } 146 | 147 | func (f StructFetcher) inBlacklist(name, parentType string) bool { 148 | for _, n := range f.blacklist[parentType] { 149 | if n == name { 150 | return true 151 | } 152 | } 153 | 154 | for _, n := range f.blacklist["*"] { 155 | if n == name { 156 | return true 157 | } 158 | } 159 | return false 160 | } 161 | 162 | func (f StructFetcher) firstName(names []*ast.Ident) string { 163 | if len(names) == 0 { 164 | return "" 165 | } 166 | 167 | return names[0].Name 168 | } 169 | 170 | func (f StructFetcher) extractType(n ast.Node) (name string, ptr, slice, isMap bool) { 171 | switch x := n.(type) { 172 | case *ast.Ident: 173 | return x.Name, false, false, false 174 | case *ast.StarExpr: 175 | name, _, _, _ := f.extractType(x.X) 176 | return name, true, false, false 177 | case *ast.SelectorExpr: 178 | pkg, _, _, _ := f.extractType(x.X) 179 | name, _, _, _ := f.extractType(x.Sel) 180 | fullName := fmt.Sprintf("%s.%s", pkg, name) 181 | 182 | if _, ok := f.knownTypes[fullName]; !ok { 183 | return "", false, false, false 184 | } 185 | 186 | return fullName, false, false, false 187 | case *ast.ArrayType: 188 | name, _, _, _ := f.extractType(x.Elt) 189 | return name, false, true, false 190 | case *ast.MapType: 191 | name, _, _, _ := f.extractType(x.Key) 192 | return name, false, false, true 193 | } 194 | 195 | return "", false, false, false 196 | } 197 | -------------------------------------------------------------------------------- /pubsub-gen/internal/inspector/struct_fetcher_test.go: -------------------------------------------------------------------------------- 1 | package inspector_test 2 | 3 | import ( 4 | "go/parser" 5 | "go/token" 6 | "testing" 7 | 8 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 9 | "github.com/poy/onpar" 10 | . "github.com/poy/onpar/expect" 11 | . "github.com/poy/onpar/matchers" 12 | ) 13 | 14 | type TSF struct { 15 | *testing.T 16 | f inspector.StructFetcher 17 | } 18 | 19 | func TestStructFetcher(t *testing.T) { 20 | t.Parallel() 21 | o := onpar.New() 22 | defer o.Run(t) 23 | 24 | o.BeforeEach(func(t *testing.T) TSF { 25 | return TSF{ 26 | T: t, 27 | f: inspector.NewStructFetcher(nil, map[string]string{"other.Type": "xx"}, nil), 28 | } 29 | }) 30 | 31 | o.Group("normal types", func() { 32 | o.Spec("it parses and returns a single struct", func(t TSF) { 33 | src := ` 34 | package p 35 | type x struct { 36 | i string 37 | j int 38 | } 39 | ` 40 | fset := token.NewFileSet() 41 | n, err := parser.ParseFile(fset, "src.go", src, 0) 42 | Expect(t, err == nil).To(BeTrue()) 43 | 44 | s, err := t.f.Parse(n) 45 | Expect(t, err == nil).To(BeTrue()) 46 | Expect(t, s).To(HaveLen(1)) 47 | Expect(t, s[0].Name).To(Equal("x")) 48 | Expect(t, s[0].Fields).To(HaveLen(2)) 49 | 50 | Expect(t, s[0].Fields[0].Name).To(Equal("i")) 51 | Expect(t, s[0].Fields[0].Type).To(Equal("string")) 52 | 53 | Expect(t, s[0].Fields[1].Name).To(Equal("j")) 54 | Expect(t, s[0].Fields[1].Type).To(Equal("int")) 55 | }) 56 | 57 | o.Spec("it parses multiple structs", func(t TSF) { 58 | src := ` 59 | package p 60 | type x struct { 61 | i string 62 | j int 63 | } 64 | 65 | type y struct { 66 | i string 67 | j int 68 | } 69 | ` 70 | fset := token.NewFileSet() 71 | n, err := parser.ParseFile(fset, "src.go", src, 0) 72 | Expect(t, err == nil).To(BeTrue()) 73 | 74 | s, err := t.f.Parse(n) 75 | Expect(t, err == nil).To(BeTrue()) 76 | 77 | Expect(t, s).To(HaveLen(2)) 78 | Expect(t, s[0].Name).To(Equal("x")) 79 | Expect(t, s[1].Name).To(Equal("y")) 80 | }) 81 | }) 82 | 83 | o.Group("pointer type", func() { 84 | o.Spec("it parses and returns a single struct", func(t TSF) { 85 | src := ` 86 | package p 87 | type x struct { 88 | i string 89 | j *Y 90 | } 91 | ` 92 | fset := token.NewFileSet() 93 | n, err := parser.ParseFile(fset, "src.go", src, 0) 94 | Expect(t, err == nil).To(BeTrue()) 95 | 96 | s, err := t.f.Parse(n) 97 | Expect(t, err == nil).To(BeTrue()) 98 | Expect(t, s).To(HaveLen(1)) 99 | Expect(t, s[0].Name).To(Equal("x")) 100 | Expect(t, s[0].Fields).To(HaveLen(2)) 101 | 102 | Expect(t, s[0].Fields[0].Name).To(Equal("i")) 103 | Expect(t, s[0].Fields[0].Type).To(Equal("string")) 104 | Expect(t, s[0].Fields[0].Ptr).To(BeFalse()) 105 | 106 | Expect(t, s[0].Fields[1].Name).To(Equal("j")) 107 | Expect(t, s[0].Fields[1].Type).To(Equal("Y")) 108 | Expect(t, s[0].Fields[1].Ptr).To(BeTrue()) 109 | }) 110 | }) 111 | 112 | o.Group("sub types", func() { 113 | o.Spec("it parses and returns a single struct", func(t TSF) { 114 | src := ` 115 | package p 116 | type x struct { 117 | i other.Type 118 | j *other.Type 119 | k dontInclude.Type 120 | } 121 | ` 122 | fset := token.NewFileSet() 123 | n, err := parser.ParseFile(fset, "src.go", src, 0) 124 | Expect(t, err == nil).To(BeTrue()) 125 | 126 | s, err := t.f.Parse(n) 127 | Expect(t, err == nil).To(BeTrue()) 128 | Expect(t, s).To(HaveLen(1)) 129 | Expect(t, s[0].Name).To(Equal("x")) 130 | Expect(t, s[0].Fields).To(HaveLen(2)) 131 | 132 | Expect(t, s[0].Fields[0].Name).To(Equal("i")) 133 | Expect(t, s[0].Fields[0].Type).To(Equal("other.Type")) 134 | Expect(t, s[0].Fields[0].Ptr).To(BeFalse()) 135 | 136 | Expect(t, s[0].Fields[1].Name).To(Equal("j")) 137 | Expect(t, s[0].Fields[1].Type).To(Equal("other.Type")) 138 | Expect(t, s[0].Fields[1].Ptr).To(BeTrue()) 139 | }) 140 | }) 141 | } 142 | 143 | func TestStructFetcherWithBasicSlices(t *testing.T) { 144 | t.Parallel() 145 | o := onpar.New() 146 | defer o.Run(t) 147 | 148 | o.BeforeEach(func(t *testing.T) TSF { 149 | return TSF{ 150 | T: t, 151 | f: inspector.NewStructFetcher(nil, nil, nil), 152 | } 153 | }) 154 | 155 | o.Spec("it parses and returns a field that is a slice of a basic type", func(t TSF) { 156 | src := ` 157 | package p 158 | type x struct { 159 | a []int 160 | b []int8 161 | c []int32 162 | d []int64 163 | e []uint 164 | f []uint8 165 | g []uint32 166 | h []uint64 167 | i []string 168 | j []float32 169 | k []float64 170 | l []bool 171 | m []byte 172 | x []unknown 173 | } 174 | ` 175 | 176 | fset := token.NewFileSet() 177 | n, err := parser.ParseFile(fset, "src.go", src, 0) 178 | Expect(t, err == nil).To(BeTrue()) 179 | 180 | s, err := t.f.Parse(n) 181 | Expect(t, err == nil).To(BeTrue()) 182 | Expect(t, s).To(HaveLen(1)) 183 | Expect(t, s[0].Name).To(Equal("x")) 184 | Expect(t, s[0].Fields).To(HaveLen(13)) 185 | 186 | for _, f := range s[0].Fields { 187 | Expect(t, f.Slice.IsSlice).To(BeTrue()) 188 | Expect(t, f.Slice.IsBasicType).To(BeTrue()) 189 | Expect(t, f.Ptr).To(BeFalse()) 190 | } 191 | }) 192 | } 193 | 194 | func TestStructFetcherWithNonBasicSlices(t *testing.T) { 195 | t.Parallel() 196 | o := onpar.New() 197 | defer o.Run(t) 198 | 199 | o.BeforeEach(func(t *testing.T) TSF { 200 | return TSF{ 201 | T: t, 202 | f: inspector.NewStructFetcher(nil, nil, map[string]string{"x.a": "myField", "x.c": ""}), 203 | } 204 | }) 205 | 206 | o.Spec("it parses and returns a field that is a slice of a non-basic type", func(t TSF) { 207 | src := ` 208 | package p 209 | type x struct { 210 | a []known 211 | b []unknown 212 | c []known 213 | } 214 | ` 215 | 216 | fset := token.NewFileSet() 217 | n, err := parser.ParseFile(fset, "src.go", src, 0) 218 | Expect(t, err == nil).To(BeTrue()) 219 | 220 | s, err := t.f.Parse(n) 221 | Expect(t, err == nil).To(BeTrue()) 222 | Expect(t, s).To(HaveLen(1)) 223 | Expect(t, s[0].Name).To(Equal("x")) 224 | Expect(t, s[0].Fields).To(HaveLen(2)) 225 | 226 | Expect(t, s[0].Fields[0].Slice.IsSlice).To(BeTrue()) 227 | Expect(t, s[0].Fields[0].Slice.IsBasicType).To(BeFalse()) 228 | Expect(t, s[0].Fields[0].Slice.FieldName).To(Equal("myField")) 229 | Expect(t, s[0].Fields[0].Ptr).To(BeFalse()) 230 | Expect(t, s[0].Fields[0].Type).To(Equal("known")) 231 | 232 | Expect(t, s[0].Fields[1].Slice.IsSlice).To(BeTrue()) 233 | Expect(t, s[0].Fields[1].Slice.IsBasicType).To(BeFalse()) 234 | Expect(t, s[0].Fields[1].Slice.FieldName).To(Equal("")) 235 | Expect(t, s[0].Fields[1].Ptr).To(BeFalse()) 236 | Expect(t, s[0].Fields[1].Type).To(Equal("known")) 237 | }) 238 | } 239 | 240 | func TestStructFetcherWithMapsWithBasicKeys(t *testing.T) { 241 | t.Parallel() 242 | o := onpar.New() 243 | defer o.Run(t) 244 | 245 | o.BeforeEach(func(t *testing.T) TSF { 246 | return TSF{ 247 | T: t, 248 | f: inspector.NewStructFetcher(nil, nil, nil), 249 | } 250 | }) 251 | 252 | o.Spec("it parses and returns a field that is a map with a basic key type", func(t TSF) { 253 | src := ` 254 | package p 255 | type x struct { 256 | a map[int]bool 257 | b map[int8]bool 258 | c map[int32]bool 259 | d map[int64]bool 260 | e map[uint]bool 261 | f map[uint8]bool 262 | g map[uint32]bool 263 | h map[uint64]bool 264 | i map[string]bool 265 | j map[float32]bool 266 | k map[float64]bool 267 | l map[bool]bool 268 | m map[byte]bool 269 | x map[unknown]bool 270 | } 271 | ` 272 | 273 | fset := token.NewFileSet() 274 | n, err := parser.ParseFile(fset, "src.go", src, 0) 275 | Expect(t, err == nil).To(BeTrue()) 276 | 277 | s, err := t.f.Parse(n) 278 | Expect(t, err == nil).To(BeTrue()) 279 | Expect(t, s).To(HaveLen(1)) 280 | Expect(t, s[0].Name).To(Equal("x")) 281 | Expect(t, s[0].Fields).To(HaveLen(13)) 282 | 283 | for _, f := range s[0].Fields { 284 | Expect(t, f.Map.IsMap).To(BeTrue()) 285 | Expect(t, f.Ptr).To(BeFalse()) 286 | } 287 | }) 288 | } 289 | 290 | func TestStructFetcherWithBlacklist(t *testing.T) { 291 | t.Parallel() 292 | o := onpar.New() 293 | defer o.Run(t) 294 | 295 | o.BeforeEach(func(t *testing.T) TSF { 296 | return TSF{ 297 | T: t, 298 | } 299 | }) 300 | 301 | o.Spec("blacklists the given struct.field combo", func(t TSF) { 302 | f := inspector.NewStructFetcher(map[string][]string{ 303 | "x": {"a", "b"}, 304 | }, nil, nil) 305 | src := ` 306 | package p 307 | type x struct { 308 | i string 309 | j int 310 | a int 311 | b int 312 | } 313 | ` 314 | fset := token.NewFileSet() 315 | n, err := parser.ParseFile(fset, "src.go", src, 0) 316 | Expect(t, err == nil).To(BeTrue()) 317 | 318 | s, err := f.Parse(n) 319 | Expect(t, err == nil).To(BeTrue()) 320 | Expect(t, s).To(HaveLen(1)) 321 | Expect(t, s[0].Name).To(Equal("x")) 322 | Expect(t, s[0].Fields).To(HaveLen(2)) 323 | 324 | Expect(t, s[0].Fields[0].Name).To(Equal("i")) 325 | Expect(t, s[0].Fields[0].Type).To(Equal("string")) 326 | 327 | Expect(t, s[0].Fields[1].Name).To(Equal("j")) 328 | Expect(t, s[0].Fields[1].Type).To(Equal("int")) 329 | }) 330 | 331 | o.Spec("blacklists the given struct.field combo with wildcard structname", func(t TSF) { 332 | f := inspector.NewStructFetcher(map[string][]string{ 333 | "*": {"a", "b"}, 334 | }, nil, nil) 335 | src := ` 336 | package p 337 | type x struct { 338 | i string 339 | j int 340 | a int 341 | b int 342 | } 343 | ` 344 | fset := token.NewFileSet() 345 | n, err := parser.ParseFile(fset, "src.go", src, 0) 346 | Expect(t, err == nil).To(BeTrue()) 347 | 348 | s, err := f.Parse(n) 349 | Expect(t, err == nil).To(BeTrue()) 350 | Expect(t, s).To(HaveLen(1)) 351 | Expect(t, s[0].Name).To(Equal("x")) 352 | Expect(t, s[0].Fields).To(HaveLen(2)) 353 | 354 | Expect(t, s[0].Fields[0].Name).To(Equal("i")) 355 | Expect(t, s[0].Fields[0].Type).To(Equal("string")) 356 | 357 | Expect(t, s[0].Fields[1].Name).To(Equal("j")) 358 | Expect(t, s[0].Fields[1].Type).To(Equal("int")) 359 | }) 360 | } 361 | -------------------------------------------------------------------------------- /pubsub-gen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/generator" 13 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/internal/inspector" 14 | ) 15 | 16 | func main() { 17 | structPath := flag.String("struct-name", "", "The name of the struct create a traverser for") 18 | packageName := flag.String("package", "", "The package name of the generated code") 19 | traverserName := flag.String("traverser", "", "The name of the generated traverser") 20 | output := flag.String("output", "", "The path to output the generated file") 21 | isPtr := flag.Bool("pointer", false, "Will the struct be a pointer when being published?") 22 | includePkgName := flag.Bool("include-pkg-name", false, "Prefix the struct type with the package name?") 23 | interfaces := flag.String("interfaces", "{}", "A map (map[string][]string encoded in JSON) mapping interface types to implementing structs") 24 | slices := flag.String("slices", "{}", "A map (map[string][]string encoded in JSON) mapping types to field names for slices") 25 | subStructs := flag.String("sub-structs", "{}", "A map (map[string]string encoded in JSON) mapping names to package locations") 26 | imports := flag.String("imports", "{}", "A map (map[string]string) of imports required in the generated file") 27 | blacklist := flag.String("blacklist-fields", "", `A comma separated list of struct name and field 28 | combos to not include (e.g., mystruct.myfield,otherthing.otherfield). 29 | A wildcard (*) can be provided for the struct name (e.g., *.fieldname).`) 30 | 31 | flag.Parse() 32 | gopath := os.Getenv("GOPATH") 33 | 34 | if gopath == "" { 35 | log.Fatal("GOPATH is empty") 36 | } 37 | 38 | if *structPath == "" { 39 | log.Fatal("struct-name is required") 40 | } 41 | 42 | if *packageName == "" { 43 | log.Fatal("package is required") 44 | } 45 | 46 | if *traverserName == "" { 47 | log.Fatal("traverser is required") 48 | } 49 | 50 | if *output == "" { 51 | log.Fatal("output is required") 52 | } 53 | 54 | idx := strings.LastIndex(filepath.ToSlash(*structPath), ".") 55 | if idx < 0 { 56 | log.Fatalf("Invalid struct name: %s", *structPath) 57 | } 58 | 59 | mi := make(map[string][]string) 60 | if err := json.Unmarshal([]byte(*interfaces), &mi); err != nil { 61 | log.Fatalf("Invalid interfaces (%s): %s", *interfaces, err) 62 | } 63 | 64 | ms := make(map[string]string) 65 | if err := json.Unmarshal([]byte(*subStructs), &ms); err != nil { 66 | log.Fatalf("Invalid sub-structs (%s): %s", *subStructs, err) 67 | } 68 | 69 | sliceM := make(map[string]string) 70 | if err := json.Unmarshal([]byte(*slices), &sliceM); err != nil { 71 | log.Fatalf("Invalid slices (%s): %s", *slices, err) 72 | } 73 | 74 | importM := make(map[string]string) 75 | if err := json.Unmarshal([]byte(*imports), &importM); err != nil { 76 | log.Fatalf("Invalid imports (%s): %s", *imports, err) 77 | } 78 | 79 | structName := (*structPath)[idx+1:] 80 | 81 | var pkgName string 82 | if *includePkgName { 83 | idx2 := strings.LastIndex(filepath.ToSlash(*structPath), "/") 84 | pkgName = filepath.ToSlash(*structPath)[idx2+1:idx] + "." 85 | } 86 | 87 | fieldBlacklist := buildBlacklist(*blacklist) 88 | 89 | sf := inspector.NewStructFetcher(fieldBlacklist, ms, sliceM) 90 | pp := inspector.NewPackageParser(sf) 91 | 92 | mm := make(map[string]inspector.Struct) 93 | for fullName, path := range ms { 94 | splitName := strings.SplitN(fullName, ".", 2) 95 | var pkg string 96 | if len(splitName) == 2 { 97 | pkg = splitName[0] 98 | } 99 | 100 | m, err := pp.Parse(path, gopath) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | 105 | for k, v := range m { 106 | v.Name = fmt.Sprintf("%s.%s", pkg, k) 107 | if strings.HasSuffix(fullName, v.Name) { 108 | mm[v.Name] = v 109 | } else { 110 | mm[k] = v 111 | } 112 | } 113 | } 114 | 115 | m, err := pp.Parse((*structPath)[:idx], gopath) 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | for k, v := range m { 120 | mm[k] = v 121 | } 122 | 123 | linker := inspector.NewLinker() 124 | linker.Link(mm, mi) 125 | 126 | g := generator.NewTraverserGenerator(generator.CodeWriter{}) 127 | src, err := g.Generate( 128 | mm, 129 | *packageName, 130 | *traverserName, 131 | structName, 132 | *isPtr, 133 | pkgName, 134 | importM, 135 | ) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | 140 | pg := generator.NewPathGenerator() 141 | src, err = pg.Generate(src, mm, *traverserName, structName) 142 | if err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | err = os.WriteFile(*output, []byte(src), 0400) 147 | if err != nil { 148 | log.Fatal(err) 149 | } 150 | } 151 | 152 | func buildBlacklist(bl string) map[string][]string { 153 | if len(bl) == 0 { 154 | return nil 155 | } 156 | 157 | m := make(map[string][]string) 158 | for _, s := range strings.Split(bl, ",") { 159 | x := strings.Split(s, ".") 160 | if len(x) != 2 { 161 | log.Fatalf("'%s' is not in the proper format (structname.fieldname)", x) 162 | } 163 | 164 | m[x[0]] = append(m[x[0]], x[1]) 165 | } 166 | return m 167 | } 168 | -------------------------------------------------------------------------------- /pubsub-gen/setters/setters.go: -------------------------------------------------------------------------------- 1 | package setters 2 | 3 | func String(s string) *string { 4 | return &s 5 | } 6 | 7 | func Int(i int) *int { 8 | return &i 9 | } 10 | 11 | func Int32(i int32) *int32 { 12 | return &i 13 | } 14 | 15 | func Int64(i int64) *int64 { 16 | return &i 17 | } 18 | 19 | func Float32(f float32) *float32 { 20 | return &f 21 | } 22 | 23 | func Float64(f float64) *float64 { 24 | return &f 25 | } 26 | 27 | func Bool(b bool) *bool { 28 | return &b 29 | } 30 | -------------------------------------------------------------------------------- /pubsub.go: -------------------------------------------------------------------------------- 1 | // Package pubsub provides a library that implements the Publish and Subscribe 2 | // model. Subscriptions can subscribe to complex data patterns and data 3 | // will be published to all subscribers that fit the criteria. 4 | // 5 | // Each Subscription when subscribing will walk the underlying subscription 6 | // tree to find its place in the tree. The given path when subscribing is used 7 | // to analyze the Subscription and find the correct node to store it in. 8 | // 9 | // As data is published, the TreeTraverser analyzes the data to determine 10 | // what nodes the data belongs to. Data is written to multiple subscriptions. 11 | // This means that when data is published, the system can 12 | // traverse multiple paths for the data. 13 | package pubsub 14 | 15 | import ( 16 | "math/rand" 17 | "sync" 18 | 19 | "code.cloudfoundry.org/go-pubsub/internal/node" 20 | ) 21 | 22 | // PubSub uses the given SubscriptionEnroller to create the subscription 23 | // tree. It also uses the TreeTraverser to then write to the subscriber. All 24 | // of PubSub's methods safe to access concurrently. PubSub should be 25 | // constructed with New(). 26 | type PubSub struct { 27 | mu rlocker 28 | n *node.Node 29 | rand func(n int64) int64 30 | deterministicRoutingHasher func(interface{}) uint64 31 | } 32 | 33 | // New constructs a new PubSub. 34 | func New(opts ...PubSubOption) *PubSub { 35 | s := &PubSub{ 36 | mu: &sync.RWMutex{}, 37 | rand: rand.Int63n, 38 | } 39 | 40 | for _, o := range opts { 41 | o.configure(s) 42 | } 43 | 44 | if s.deterministicRoutingHasher == nil { 45 | s.deterministicRoutingHasher = func(_ interface{}) uint64 { 46 | return uint64(s.rand(0x7FFFFFFFFFFFFFFF)) 47 | } 48 | } 49 | 50 | s.n = node.New(s.rand) 51 | 52 | return s 53 | } 54 | 55 | // PubSubOption is used to configure a PubSub. 56 | type PubSubOption interface { 57 | configure(*PubSub) 58 | } 59 | 60 | type pubsubConfigFunc func(*PubSub) 61 | 62 | func (f pubsubConfigFunc) configure(s *PubSub) { 63 | f(s) 64 | } 65 | 66 | // WithNoMutex configures a PubSub that does not have any internal mutexes. 67 | // This is useful if more complex or custom locking is required. For example, 68 | // if a subscription needs to subscribe while being published to. 69 | func WithNoMutex() PubSubOption { 70 | return pubsubConfigFunc(func(s *PubSub) { 71 | s.mu = nopLock{} 72 | }) 73 | } 74 | 75 | // WithRand configures a PubSub that will use the given function to make 76 | // sharding decisions. The given function has to match the symantics of 77 | // math/rand.Int63n. 78 | func WithRand(int63 func(max int64) int64) PubSubOption { 79 | return pubsubConfigFunc(func(s *PubSub) { 80 | s.rand = int63 81 | }) 82 | } 83 | 84 | // WithDeterministicHashing configures a PubSub that will use the given 85 | // function to hash each published data point. The hash is used only for a 86 | // subscription that has set its deterministic routing name. 87 | func WithDeterministicHashing(hashFunction func(interface{}) uint64) PubSubOption { 88 | return pubsubConfigFunc(func(s *PubSub) { 89 | s.deterministicRoutingHasher = hashFunction 90 | }) 91 | } 92 | 93 | // Subscription is a subscription that will have corresponding data written 94 | // to it. 95 | type Subscription func(data interface{}) 96 | 97 | // Unsubscriber is returned by Subscribe. It should be invoked to 98 | // remove a subscription from the PubSub. 99 | type Unsubscriber func() 100 | 101 | // SubscribeOption is used to configure a subscription while subscribing. 102 | type SubscribeOption interface { 103 | configure(*subscribeConfig) 104 | } 105 | 106 | // WithShardID configures a subscription to have a shardID. Subscriptions with 107 | // a shardID are sharded to any subscriptions with the same shardID and path. 108 | // Defaults to an empty shardID (meaning it does not shard). 109 | func WithShardID(shardID string) SubscribeOption { 110 | return subscribeConfigFunc(func(c *subscribeConfig) { 111 | c.shardID = shardID 112 | }) 113 | } 114 | 115 | // WithPath configures a subscription to reside at a path. The path determines 116 | // what data the subscription is interested in. This value should be 117 | // correspond to what the publishing TreeTraverser yields. 118 | // It defaults to nil (meaning it gets everything). 119 | func WithPath(path []uint64) SubscribeOption { 120 | return subscribeConfigFunc(func(c *subscribeConfig) { 121 | c.path = path 122 | }) 123 | } 124 | 125 | // WithDeterministicRouting configures a subscription to have a deterministic 126 | // routing name. A PubSub configured to use deterministic hashing will use 127 | // this name and the subscription's shard ID to maintain consistent routing. 128 | func WithDeterministicRouting(name string) SubscribeOption { 129 | return subscribeConfigFunc(func(c *subscribeConfig) { 130 | c.deterministicRoutingName = name 131 | }) 132 | } 133 | 134 | type subscribeConfig struct { 135 | shardID string 136 | deterministicRoutingName string 137 | path []uint64 138 | } 139 | 140 | type subscribeConfigFunc func(*subscribeConfig) 141 | 142 | func (f subscribeConfigFunc) configure(c *subscribeConfig) { 143 | f(c) 144 | } 145 | 146 | // Subscribe will add a subscription to the PubSub. It returns a function 147 | // that can be used to unsubscribe. Options can be provided to configure 148 | // the subscription and its interactions with published data. 149 | func (s *PubSub) Subscribe(sub Subscription, opts ...SubscribeOption) Unsubscriber { 150 | c := subscribeConfig{} 151 | for _, o := range opts { 152 | o.configure(&c) 153 | } 154 | 155 | s.mu.Lock() 156 | defer s.mu.Unlock() 157 | 158 | n := s.n 159 | for _, p := range c.path { 160 | n = n.AddChild(p) 161 | } 162 | id := n.AddSubscription(sub, c.shardID, c.deterministicRoutingName) 163 | 164 | return func() { 165 | s.mu.Lock() 166 | defer s.mu.Unlock() 167 | 168 | s.cleanupSubscriptionTree(s.n, id, c.path) 169 | } 170 | } 171 | 172 | func (s *PubSub) cleanupSubscriptionTree(n *node.Node, id int64, p []uint64) { 173 | if len(p) == 0 { 174 | n.DeleteSubscription(id) 175 | return 176 | } 177 | 178 | child := n.FetchChild(p[0]) 179 | s.cleanupSubscriptionTree(child, id, p[1:]) 180 | 181 | if child.ChildLen() == 0 && child.SubscriptionLen() == 0 { 182 | n.DeleteChild(p[0]) 183 | } 184 | } 185 | 186 | // TreeTraverser publishes data to the correct subscriptions. Each 187 | // data point can be published to several subscriptions. As the data traverses 188 | // the given paths, it will write to any subscribers that are assigned there. 189 | // Data can go down multiple paths (i.e., len(paths) > 1). 190 | // 191 | // Traversing a path ends when the return len(paths) == 0. If 192 | // len(paths) > 1, then each path will be traversed. 193 | type TreeTraverser func(data interface{}) Paths 194 | 195 | // LinearTreeTraverser implements TreeTraverser on behalf of a slice of paths. 196 | // If the data does not traverse multiple paths, then this works well. 197 | func LinearTreeTraverser(a []uint64) TreeTraverser { 198 | return func(data interface{}) Paths { 199 | if len(a) == 0 { 200 | return FlatPaths(nil) 201 | } 202 | 203 | return PathsWithTraverser([]uint64{a[0]}, LinearTreeTraverser(a[1:])) 204 | } 205 | } 206 | 207 | // Paths is returned by a TreeTraverser. It describes how the data is 208 | // both assigned and how to continue to analyze it. 209 | // At will be called with idx ranging from [0, n] where n is the number 210 | // of valid paths. This means that the Paths needs to be prepared 211 | // for an idx that is greater than it has valid data for. 212 | // 213 | // If nextTraverser is nil, then the previous TreeTraverser is used. 214 | type Paths func(idx int, data interface{}) (path uint64, nextTraverser TreeTraverser, ok bool) 215 | 216 | // CombinePaths takes several paths and flattens it into a single path. 217 | func CombinePaths(p ...Paths) Paths { 218 | var currentStart int 219 | return Paths(func(idx int, data interface{}) (path uint64, nextTraverser TreeTraverser, ok bool) { 220 | for _, pp := range p { 221 | path, next, ok := pp(idx-currentStart, data) 222 | if ok { 223 | return path, next, ok 224 | } 225 | 226 | if len(p) == 0 { 227 | break 228 | } 229 | 230 | currentStart = idx 231 | p = p[1:] 232 | } 233 | return 0, nil, false 234 | }) 235 | } 236 | 237 | // FlatPaths implements Paths for a slice of paths. It 238 | // returns nil for all nextTraverser meaning to use the given TreeTraverser. 239 | func FlatPaths(p []uint64) Paths { 240 | return func(idx int, data interface{}) (uint64, TreeTraverser, bool) { 241 | if idx >= len(p) { 242 | return 0, nil, false 243 | } 244 | 245 | return p[idx], nil, true 246 | } 247 | } 248 | 249 | // PathsWithTraverser implements Paths for both a slice of paths and 250 | // a single TreeTraverser. Each path will return the given TreeTraverser. 251 | func PathsWithTraverser(paths []uint64, a TreeTraverser) Paths { 252 | return func(idx int, data interface{}) (uint64, TreeTraverser, bool) { 253 | if idx >= len(paths) { 254 | return 0, nil, false 255 | } 256 | 257 | return paths[idx], a, true 258 | } 259 | } 260 | 261 | // PathAndTraverser is a path and traverser pair. 262 | type PathAndTraverser struct { 263 | Path uint64 264 | Traverser TreeTraverser 265 | } 266 | 267 | // PathsWithTraverser implement Paths and allow a TreeTraverser to have 268 | // multiple paths with multiple traversers. 269 | func PathAndTraversers(t []PathAndTraverser) Paths { 270 | return func(idx int, data interface{}) (uint64, TreeTraverser, bool) { 271 | if idx >= len(t) { 272 | return 0, nil, false 273 | } 274 | 275 | return t[idx].Path, t[idx].Traverser, true 276 | } 277 | } 278 | 279 | // Publish writes data using the TreeTraverser to the interested subscriptions. 280 | func (s *PubSub) Publish(d interface{}, a TreeTraverser) { 281 | s.mu.RLock() 282 | defer s.mu.RUnlock() 283 | s.traversePublish(d, d, a, s.n) 284 | } 285 | 286 | func (s *PubSub) traversePublish(d, next interface{}, a TreeTraverser, n *node.Node) { 287 | if n == nil { 288 | return 289 | } 290 | n.ForEachSubscription(func(shardID string, isDeterministic bool, ss []node.SubscriptionEnvelope) { 291 | if shardID == "" { 292 | for _, x := range ss { 293 | x.Subscription(d) 294 | } 295 | return 296 | } 297 | 298 | idx := s.determineIdx(d, len(ss), isDeterministic) 299 | ss[idx].Subscription(d) 300 | }) 301 | 302 | paths := a(next) 303 | 304 | for i := 0; ; i++ { 305 | child, nextA, ok := paths(i, next) 306 | if !ok { 307 | return 308 | } 309 | 310 | if nextA == nil { 311 | nextA = a 312 | } 313 | 314 | c := n.FetchChild(child) 315 | 316 | s.traversePublish(d, next, nextA, c) 317 | } 318 | } 319 | 320 | func (s *PubSub) determineIdx(d interface{}, l int, isDeterministic bool) int64 { 321 | if isDeterministic { 322 | return int64(s.deterministicRoutingHasher(d) % uint64(l)) 323 | } 324 | return s.rand(int64(l)) 325 | } 326 | 327 | // rlocker is used to hold either a real sync.RWMutex or a nop lock. 328 | // This is used to turn off locking. 329 | type rlocker interface { 330 | sync.Locker 331 | RLock() 332 | RUnlock() 333 | } 334 | 335 | // nopLock is used to turn off locking for the PubSub. It implements the 336 | // rlocker interface. 337 | type nopLock struct{} 338 | 339 | // Lock implements rlocker. 340 | func (l nopLock) Lock() {} 341 | 342 | // Unlock implements rlocker. 343 | func (l nopLock) Unlock() {} 344 | 345 | // RLock implements rlocker. 346 | func (l nopLock) RLock() {} 347 | 348 | // RUnlock implements rlocker. 349 | func (l nopLock) RUnlock() {} 350 | -------------------------------------------------------------------------------- /pubsub_test.go: -------------------------------------------------------------------------------- 1 | package pubsub_test 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | "testing" 7 | 8 | "code.cloudfoundry.org/go-pubsub" 9 | "code.cloudfoundry.org/go-pubsub/pubsub-gen/setters" 10 | "github.com/poy/onpar" 11 | . "github.com/poy/onpar/expect" 12 | . "github.com/poy/onpar/matchers" 13 | ) 14 | 15 | type TPS struct { 16 | *testing.T 17 | p *pubsub.PubSub 18 | subscription *spySubscription 19 | sub func(interface{}) 20 | } 21 | 22 | //go:generate go install code.cloudfoundry.org/go-pubsub/pubsub-gen 23 | //go:generate $GOPATH/bin/pubsub-gen --output=$GOPATH/src/code.cloudfoundry.org/go-pubsub/gen_struct_test.go --pointer --struct-name=code.cloudfoundry.org/go-pubsub.testStruct --traverser=testStructTrav --package=pubsub_test 24 | type testStruct struct { 25 | a int 26 | b int 27 | aa *testStructA 28 | bb *testStructB 29 | } 30 | 31 | type testStructA struct { 32 | a int 33 | } 34 | 35 | type testStructB struct { 36 | b int 37 | } 38 | 39 | func TestPubSub(t *testing.T) { 40 | t.Parallel() 41 | o := onpar.New() 42 | defer o.Run(t) 43 | o.BeforeEach(func(t *testing.T) TPS { 44 | s, f := newSpySubscrption() 45 | 46 | return TPS{ 47 | T: t, 48 | sub: f, 49 | subscription: s, 50 | p: pubsub.New(), 51 | } 52 | }) 53 | 54 | o.Spec("it writes to the correct subscription", func(t TPS) { 55 | sub1, f1 := newSpySubscrption() 56 | sub2, f2 := newSpySubscrption() 57 | sub3, f3 := newSpySubscrption() 58 | sub4, f4 := newSpySubscrption() 59 | 60 | t.p.Subscribe(f1, pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 61 | a: setters.Int(1), 62 | b: setters.Int(2), 63 | }))) 64 | t.p.Subscribe(f2, pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 65 | a: setters.Int(1), 66 | b: setters.Int(3), 67 | }))) 68 | t.p.Subscribe(f3, pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 69 | a: setters.Int(1), 70 | }))) 71 | t.p.Subscribe(f4, pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 72 | a: setters.Int(2), 73 | b: setters.Int(3), 74 | aa: &testStructAFilter{ 75 | a: setters.Int(4), 76 | }, 77 | }))) 78 | 79 | data := &testStruct{a: 1, b: 2} 80 | otherData := &testStruct{a: 2, b: 3, aa: &testStructA{a: 4}} 81 | t.p.Publish(data, testStructTravTraverse) 82 | t.p.Publish(otherData, testStructTravTraverse) 83 | 84 | Expect(t, sub1.data).To(HaveLen(1)) 85 | Expect(t, sub2.data).To(HaveLen(0)) 86 | Expect(t, sub3.data).To(HaveLen(1)) 87 | Expect(t, sub4.data).To(HaveLen(1)) 88 | 89 | Expect(t, sub1.data[0]).To(Equal(data)) 90 | Expect(t, sub3.data[0]).To(Equal(data)) 91 | Expect(t, sub4.data[0]).To(Equal(otherData)) 92 | }) 93 | 94 | o.Spec("it does not write to a subscription after it unsubscribes", func(t TPS) { 95 | sub, f := newSpySubscrption() 96 | unsubscribe := t.p.Subscribe(f, pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 97 | a: setters.Int(1), 98 | b: setters.Int(2), 99 | }))) 100 | 101 | unsubscribe() 102 | 103 | t.p.Publish(&testStruct{a: 1, b: 2}, testStructTravTraverse) 104 | Expect(t, sub.data).To(HaveLen(0)) 105 | }) 106 | } 107 | 108 | func TestPubSubWithShardID(t *testing.T) { 109 | t.Parallel() 110 | o := onpar.New() 111 | defer o.Run(t) 112 | o.BeforeEach(func(t *testing.T) TPS { 113 | s, f := newSpySubscrption() 114 | 115 | return TPS{ 116 | T: t, 117 | subscription: s, 118 | sub: f, 119 | p: pubsub.New(pubsub.WithRand(rand.Int63n)), 120 | } 121 | }) 122 | 123 | o.Spec("it splits data between same shardIDs", func(t TPS) { 124 | sub1, f1 := newSpySubscrption() 125 | sub2, f2 := newSpySubscrption() 126 | sub3, f3 := newSpySubscrption() 127 | sub4, f4 := newSpySubscrption() 128 | sub5, f5 := newSpySubscrption() 129 | 130 | t.p.Subscribe(f1, 131 | pubsub.WithShardID("1"), 132 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 133 | a: setters.Int(1), 134 | })), 135 | ) 136 | 137 | t.p.Subscribe(f2, 138 | pubsub.WithShardID("1"), 139 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 140 | a: setters.Int(1), 141 | })), 142 | ) 143 | t.p.Subscribe(f3, 144 | pubsub.WithShardID("2"), 145 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 146 | a: setters.Int(1), 147 | })), 148 | ) 149 | 150 | t.p.Subscribe(f4, 151 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 152 | a: setters.Int(1), 153 | })), 154 | ) 155 | 156 | t.p.Subscribe(f5, 157 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{ 158 | a: setters.Int(1), 159 | })), 160 | ) 161 | 162 | for i := 0; i < 100; i++ { 163 | t.p.Publish(&testStruct{a: 1, b: 2}, testStructTravTraverse) 164 | } 165 | 166 | Expect(t, len(sub1.data)).To(And(BeAbove(0), BeBelow(99))) 167 | Expect(t, len(sub2.data)).To(And(BeAbove(0), BeBelow(99))) 168 | Expect(t, len(sub3.data)).To(Equal(100)) 169 | Expect(t, len(sub4.data)).To(Equal(100)) 170 | Expect(t, len(sub5.data)).To(Equal(100)) 171 | }) 172 | } 173 | 174 | func TestPubSubWithShardingScheme(t *testing.T) { 175 | t.Parallel() 176 | o := onpar.New() 177 | defer o.Run(t) 178 | o.BeforeEach(func(t *testing.T) TPS { 179 | s, f := newSpySubscrption() 180 | 181 | return TPS{ 182 | T: t, 183 | subscription: s, 184 | sub: f, 185 | p: pubsub.New(pubsub.WithDeterministicHashing(func(data interface{}) uint64 { 186 | return uint64(data.(*testStruct).a) 187 | })), 188 | } 189 | }) 190 | 191 | o.Spec("it splits data between same shardIDs", func(t TPS) { 192 | sub1, f1 := newSpySubscrption() 193 | sub2, f2 := newSpySubscrption() 194 | sub3, f3 := newSpySubscrption() 195 | sub4, f4 := newSpySubscrption() 196 | sub5, f5 := newSpySubscrption() 197 | 198 | t.p.Subscribe(f1, 199 | pubsub.WithShardID("1"), 200 | pubsub.WithDeterministicRouting("black"), 201 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{})), 202 | ) 203 | 204 | t.p.Subscribe(f2, 205 | pubsub.WithShardID("1"), 206 | pubsub.WithDeterministicRouting("blue"), 207 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{})), 208 | ) 209 | t.p.Subscribe(f3, 210 | pubsub.WithShardID("2"), 211 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{})), 212 | ) 213 | 214 | t.p.Subscribe(f4, 215 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{})), 216 | ) 217 | 218 | t.p.Subscribe(f5, 219 | pubsub.WithPath(testStructTravCreatePath(&testStructFilter{})), 220 | ) 221 | 222 | for i := 0; i < 100; i++ { 223 | t.p.Publish(&testStruct{a: 1, b: 2}, testStructTravTraverse) 224 | t.p.Publish(&testStruct{a: 2, b: 3}, testStructTravTraverse) 225 | } 226 | 227 | Expect(t, len(sub1.data)).To(Equal(100)) 228 | Expect(t, len(sub2.data)).To(Equal(100)) 229 | 230 | Expect(t, len(sub3.data)).To(Equal(200)) 231 | Expect(t, len(sub4.data)).To(Equal(200)) 232 | Expect(t, len(sub5.data)).To(Equal(200)) 233 | 234 | Expect(t, sub1.data[0]).To(Or( 235 | Equal(&testStruct{a: 1, b: 2}), 236 | Equal(&testStruct{a: 2, b: 3}), 237 | )) 238 | 239 | Expect(t, sub2.data[0]).To(Or( 240 | Equal(&testStruct{a: 1, b: 2}), 241 | Equal(&testStruct{a: 2, b: 3}), 242 | )) 243 | Expect(t, sub1.data[0]).To(Not(Equal(sub2.data[0]))) 244 | }) 245 | } 246 | 247 | type spySubscription struct { 248 | mu sync.Mutex 249 | data []interface{} 250 | } 251 | 252 | func newSpySubscrption() (*spySubscription, func(interface{})) { 253 | s := &spySubscription{} 254 | return s, func(data interface{}) { 255 | s.mu.Lock() 256 | defer s.mu.Unlock() 257 | s.data = append(s.data, data) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.14.x 5 | - 1.15.x 6 | 7 | install: 8 | - go get -d -v -t ./... 9 | 10 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Andrew Poydence 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/README.md: -------------------------------------------------------------------------------- 1 | # onpar 2 | [![GoDoc][go-doc-badge]][go-doc] [![travis][travis-badge]][travis] 3 | 4 | Parallel testing framework for Go 5 | 6 | ### Assertions 7 | OnPar provides built-in assertion mechanisms using `Expect` statement to make expectations. Here is some more information about `Expect` and some of the matchers that come with OnPar. 8 | 9 | - [Expect](expect/README.md) 10 | - [Matchers](matchers/README.md) 11 | 12 | ### Specs 13 | Test assertions are done within a `Spec()` function. Each `Spec` has a name and a function. The function takes a `testing.T` as an argument and any output from a `BeforeEach()`. Each `Spec` is run in parallel (`t.Parallel()` is invoked for each spec before calling the given function). 14 | 15 | ```go 16 | func TestSpecs(t *testing.T) { 17 | o := onpar.New() 18 | defer o.Run(t) 19 | 20 | o.BeforeEach(func(t *testing.T) (*testing.T, int, float64) { 21 | return t, 99, 101.0 22 | }) 23 | 24 | o.AfterEach(func(t *testing.T, a int, b float64) { 25 | // ... 26 | }) 27 | 28 | o.Spec("something informative", func(t *testing.T, a int, b float64) { 29 | if a != 99 { 30 | t.Errorf("%d != 99", a) 31 | } 32 | }) 33 | } 34 | ``` 35 | 36 | ### Grouping 37 | `Group`s are used to keep `Spec`s in logical place. The intention is to gather each `Spec` in a reasonable place. Each `Group` can have a `BeforeEach()` and a `AfterEach()` but are not required to. 38 | 39 | 40 | ```go 41 | func TestGrouping(t *testing.T) { 42 | o := onpar.New() 43 | defer o.Run(t) 44 | 45 | o.BeforeEach(func(t *testing.T) (*testing.T, int, float64) { 46 | return t, 99, 101.0 47 | }) 48 | 49 | o.Group("some-group", func() { 50 | o.BeforeEach(func(t *teting.T, a int, b float64) (*testing.T, string) { 51 | return t, "foo" 52 | }) 53 | 54 | o.AfterEach(func(t *testing.T, s string) { 55 | // ... 56 | }) 57 | 58 | o.Spec("something informative", func(t *testing.T, s string) { 59 | // ... 60 | }) 61 | }) 62 | } 63 | ``` 64 | 65 | ### Run Order 66 | Each `BeforeEach()` runs before any `Spec` in the same `Group`. It will also run before any sub-group `Spec`s and their `BeforeEach`es. Any `AfterEach()` will run after the `Spec` and before parent `AfterEach`es. 67 | 68 | ``` go 69 | func TestRunOrder(t *testing.T) { 70 | o := onpar.New() 71 | defer o.Run(t) 72 | 73 | o.BeforeEach(func(t *testing.T) (*testing.T, int, string) { 74 | // Spec "A": Order = 1 75 | // Spec "B": Order = 1 76 | // Spec "C": Order = 1 77 | return t, 99, "foo" 78 | }) 79 | 80 | o.AfterEach(func(t *testing.T, i int, s string) { 81 | // Spec "A": Order = 4 82 | // Spec "B": Order = 6 83 | // Spec "C": Order = 6 84 | }) 85 | 86 | o.Group("DA", func() { 87 | o.AfterEach(func(t *testing.T, i int, s string) { 88 | // Spec "A": Order = 3 89 | // Spec "B": Order = 5 90 | // Spec "C": Order = 5 91 | }) 92 | 93 | o.Spec("A", func(t *testing.T, i int, s string) { 94 | // Spec "A": Order = 2 95 | }) 96 | 97 | o.Group("DB", func() { 98 | o.BeforeEach(func(t *testing.T, i int, s string) (*testing.T, float64) { 99 | // Spec "B": Order = 2 100 | // Spec "C": Order = 2 101 | return t, 101 102 | }) 103 | 104 | o.AfterEach(func(t *testing.T, f float64) { 105 | // Spec "B": Order = 4 106 | // Spec "C": Order = 4 107 | }) 108 | 109 | o.Spec("B", func(t *testing.T, f float64) { 110 | // Spec "B": Order = 3 111 | }) 112 | 113 | o.Spec("C", func(t *testing.T, f float64) { 114 | // Spec "C": Order = 3 115 | }) 116 | }) 117 | 118 | o.Group("DC", func() { 119 | o.BeforeEach(func(t *testing.T, i int, s string) *testing.T { 120 | // Will not be invoked 121 | }) 122 | 123 | o.AfterEach(func(t *testing.T) { 124 | // Will not be invoked 125 | }) 126 | }) 127 | }) 128 | } 129 | ``` 130 | 131 | ### Avoiding Closure 132 | Why bother with returning values from a `BeforeEach`? To avoid closure of course! When running `Spec`s in parallel (which they always do), each variable needs a new instance to avoid race conditions. If you use closure, then this gets tough. So onpar will pass the arguments to the given function returned by the `BeforeEach`. 133 | 134 | The `BeforeEach` is a gatekeeper for arguments. The returned values from `BeforeEach` are required for the following `Spec`s. Child `Group`s are also passed what their direct parent `BeforeEach` returns. 135 | 136 | [go-doc-badge]: https://godoc.org/github.com/poy/onpar?status.svg 137 | [go-doc]: https://godoc.org/github.com/poy/onpar 138 | [travis-badge]: https://travis-ci.org/poy/onpar.svg?branch=master 139 | [travis]: https://travis-ci.org/poy/onpar?branch=master 140 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/diff/diff.go: -------------------------------------------------------------------------------- 1 | package diff 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // Opt is an option type that can be passed to New. 10 | // 11 | // Most of the time, you'll want to use at least one 12 | // of Actual or Expected, to differentiate the two 13 | // in your output. 14 | type Opt func(Differ) Differ 15 | 16 | // WithFormat returns an Opt that wraps up differences 17 | // using a format string. The format should contain 18 | // one '%s' to add the difference string in. 19 | func WithFormat(format string) Opt { 20 | return func(d Differ) Differ { 21 | d.wrappers = append(d.wrappers, func(v string) string { 22 | return fmt.Sprintf(format, v) 23 | }) 24 | return d 25 | } 26 | } 27 | 28 | // Sprinter is any type which can print a string. 29 | type Sprinter interface { 30 | Sprint(...interface{}) string 31 | } 32 | 33 | // WithSprinter returns an Opt that wraps up differences 34 | // using a Sprinter. 35 | func WithSprinter(s Sprinter) Opt { 36 | return func(d Differ) Differ { 37 | d.wrappers = append(d.wrappers, func(v string) string { 38 | return s.Sprint(v) 39 | }) 40 | return d 41 | } 42 | } 43 | 44 | func applyOpts(o *Differ, opts ...Opt) { 45 | for _, opt := range opts { 46 | *o = opt(*o) 47 | } 48 | } 49 | 50 | // Actual returns an Opt that only applies other Opt values 51 | // to the actual value. 52 | func Actual(opts ...Opt) Opt { 53 | return func(d Differ) Differ { 54 | if d.actual == nil { 55 | d.actual = &Differ{} 56 | } 57 | applyOpts(d.actual, opts...) 58 | return d 59 | } 60 | } 61 | 62 | // Expected returns an Opt that only applies other Opt values 63 | // to the expected value. 64 | func Expected(opts ...Opt) Opt { 65 | return func(d Differ) Differ { 66 | if d.expected == nil { 67 | d.expected = &Differ{} 68 | } 69 | applyOpts(d.expected, opts...) 70 | return d 71 | } 72 | } 73 | 74 | // Differ is a type that can diff values. It keeps its own 75 | // diffing style. 76 | type Differ struct { 77 | wrappers []func(string) string 78 | 79 | actual *Differ 80 | expected *Differ 81 | } 82 | 83 | // New creates a Differ, using the passed in opts to manipulate 84 | // its diffing behavior and output. 85 | // 86 | // If opts is empty, the default options used will be: 87 | // [ WithFormat(">%s<"), Actual(WithFormat("%s!=")) ] 88 | // 89 | // opts will be applied to the text in the order they 90 | // are passed in, so you can do things like color a value 91 | // and then wrap the colored text up in custom formatting. 92 | // 93 | // See the examples on the different Opt types for more 94 | // detail. 95 | func New(opts ...Opt) *Differ { 96 | d := Differ{} 97 | if len(opts) == 0 { 98 | opts = []Opt{WithFormat(">%s<"), Actual(WithFormat("%s!="))} 99 | } 100 | for _, opt := range opts { 101 | d = opt(d) 102 | } 103 | return &d 104 | } 105 | 106 | // format is used to format a string using the wrapper functions. 107 | func (d Differ) format(v string) string { 108 | for _, w := range d.wrappers { 109 | v = w(v) 110 | } 111 | return v 112 | } 113 | 114 | // Diff takes two values and returns a string showing a 115 | // diff of them. 116 | func (d *Differ) Diff(actual, expected interface{}) string { 117 | return d.diff(reflect.ValueOf(actual), reflect.ValueOf(expected)) 118 | } 119 | 120 | func (d *Differ) genDiff(format string, actual, expected interface{}) string { 121 | afmt := fmt.Sprintf(format, actual) 122 | if d.actual != nil { 123 | afmt = d.actual.format(afmt) 124 | } 125 | efmt := fmt.Sprintf(format, expected) 126 | if d.expected != nil { 127 | efmt = d.expected.format(efmt) 128 | } 129 | return d.format(afmt + efmt) 130 | } 131 | 132 | func (d *Differ) diff(av, ev reflect.Value) string { 133 | if !av.IsValid() { 134 | if !ev.IsValid() { 135 | return "" 136 | } 137 | if ev.Kind() == reflect.Ptr { 138 | return d.diff(av, ev.Elem()) 139 | } 140 | return d.genDiff("%v", "", ev.Interface()) 141 | } 142 | if !ev.IsValid() { 143 | if av.Kind() == reflect.Ptr { 144 | return d.diff(av.Elem(), ev) 145 | } 146 | return d.genDiff("%v", av.Interface(), "") 147 | } 148 | 149 | if av.Kind() != ev.Kind() { 150 | return d.genDiff("%s", av.Type(), ev.Type()) 151 | } 152 | 153 | if av.CanInterface() { 154 | switch av.Interface().(type) { 155 | case []rune, []byte, string: 156 | return d.strDiff(av, ev) 157 | } 158 | } 159 | 160 | switch av.Kind() { 161 | case reflect.Ptr, reflect.Interface: 162 | return d.diff(av.Elem(), ev.Elem()) 163 | case reflect.Slice, reflect.Array, reflect.String: 164 | if av.Len() != ev.Len() { 165 | // TODO: do a more thorough diff of values 166 | return d.genDiff(fmt.Sprintf("%s(len %%d)", av.Type()), av.Len(), ev.Len()) 167 | } 168 | var elems []string 169 | for i := 0; i < av.Len(); i++ { 170 | elems = append(elems, d.diff(av.Index(i), ev.Index(i))) 171 | } 172 | return "[ " + strings.Join(elems, ", ") + " ]" 173 | case reflect.Map: 174 | var parts []string 175 | for _, kv := range ev.MapKeys() { 176 | k := kv.Interface() 177 | bmv := ev.MapIndex(kv) 178 | amv := av.MapIndex(kv) 179 | if !amv.IsValid() { 180 | parts = append(parts, d.genDiff("%s", fmt.Sprintf("missing key %v", k), fmt.Sprintf("%v: %v", k, bmv.Interface()))) 181 | continue 182 | } 183 | parts = append(parts, fmt.Sprintf("%v: %s", k, d.diff(amv, bmv))) 184 | } 185 | for _, kv := range av.MapKeys() { 186 | // We've already compared all keys that exist in both maps; now we're 187 | // just looking for keys that only exist in a. 188 | if !ev.MapIndex(kv).IsValid() { 189 | k := kv.Interface() 190 | parts = append(parts, d.genDiff("%s", fmt.Sprintf("extra key %v: %v", k, av.MapIndex(kv).Interface()), fmt.Sprintf("%v: nil", k))) 191 | continue 192 | } 193 | } 194 | return "{" + strings.Join(parts, ", ") + "}" 195 | case reflect.Struct: 196 | if av.Type().Name() != ev.Type().Name() { 197 | return d.genDiff("%s", av.Type().Name(), ev.Type().Name()) + "(mismatched types)" 198 | } 199 | var parts []string 200 | for i := 0; i < ev.NumField(); i++ { 201 | f := ev.Type().Field(i) 202 | if f.PkgPath != "" && !f.Anonymous { 203 | // unexported 204 | continue 205 | } 206 | name := f.Name 207 | bfv := ev.Field(i) 208 | afv := av.Field(i) 209 | parts = append(parts, fmt.Sprintf("%s: %s", name, d.diff(afv, bfv))) 210 | } 211 | return fmt.Sprintf("%s{%s}", av.Type(), strings.Join(parts, ", ")) 212 | default: 213 | if av.Type().Comparable() { 214 | a, b := av.Interface(), ev.Interface() 215 | if a != b { 216 | return d.genDiff("%#v", a, b) 217 | } 218 | return fmt.Sprintf("%#v", a) 219 | } 220 | return d.format(fmt.Sprintf("UNSUPPORTED: could not compare values of type %s", av.Type())) 221 | } 222 | } 223 | 224 | // strDiff helps us generate a diff between two strings. 225 | // 226 | // TODO: make this maybe less naive? string diffs are hard. 227 | func (d *Differ) strDiff(av, ev reflect.Value) string { 228 | strTyp := reflect.TypeOf("") 229 | var out string 230 | 231 | diffs := smallestDiff(av, ev, 0, 0) 232 | i := 0 233 | for _, diff := range diffs { 234 | out += av.Slice(i, diff.aStart).Convert(strTyp).Interface().(string) 235 | curra := av.Slice(diff.aStart, diff.aEnd).Convert(strTyp).Interface().(string) 236 | curre := ev.Slice(diff.eStart, diff.eEnd).Convert(strTyp).Interface().(string) 237 | out += d.genDiff("%s", curra, curre) 238 | i = diff.aEnd 239 | } 240 | out += av.Slice(i, av.Len()).Convert(strTyp).Interface().(string) 241 | return out 242 | } 243 | 244 | type diffs []difference 245 | 246 | func (d diffs) cost() int { 247 | cost := 0 248 | for _, diff := range d { 249 | cost += diff.cost() 250 | } 251 | return cost 252 | } 253 | 254 | type difference struct { 255 | aStart, aEnd, eStart, eEnd int 256 | } 257 | 258 | func (d difference) cost() int { 259 | return greater(d.aEnd-d.aStart, d.eEnd-d.eStart) 260 | } 261 | 262 | func greater(a, b int) int { 263 | if a > b { 264 | return a 265 | } 266 | return b 267 | } 268 | 269 | type diffIdx struct { 270 | aStart, eStart int 271 | } 272 | 273 | func smallestDiff(av, ev reflect.Value, aStart, eStart int) []difference { 274 | cache := make(map[diffIdx][]difference) 275 | return smallestCachingDiff(cache, av, ev, aStart, eStart) 276 | } 277 | 278 | func smallestCachingDiff(cache map[diffIdx][]difference, av, ev reflect.Value, aStart, eStart int) []difference { 279 | for aStart < av.Len() && eStart < ev.Len() && av.Index(aStart).Interface() == ev.Index(eStart).Interface() { 280 | aStart++ 281 | eStart++ 282 | } 283 | if d, ok := cache[diffIdx{aStart: aStart, eStart: eStart}]; ok { 284 | return d 285 | } 286 | if aStart == av.Len() && eStart == ev.Len() { 287 | return nil 288 | } 289 | 290 | shortest := diffs{{aStart: aStart, aEnd: av.Len(), eStart: eStart, eEnd: ev.Len()}} 291 | if aStart == av.Len() || eStart == ev.Len() { 292 | return shortest 293 | } 294 | for i := aStart; i < av.Len(); i++ { 295 | for j := eStart; j < ev.Len(); j++ { 296 | if av.Index(i).Interface() != ev.Index(j).Interface() { 297 | continue 298 | } 299 | currDiffs := append(diffs{{ 300 | aStart: aStart, 301 | aEnd: i, 302 | eStart: eStart, 303 | eEnd: j, 304 | }}, smallestCachingDiff(cache, av, ev, i+1, j+1)...) 305 | if currDiffs.cost() < shortest.cost() { 306 | shortest = currDiffs 307 | } 308 | } 309 | } 310 | cache[diffIdx{aStart: aStart, eStart: eStart}] = shortest 311 | return shortest 312 | } 313 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/diff/doc.go: -------------------------------------------------------------------------------- 1 | //go:generate hel 2 | 3 | package diff 4 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/expect/README.md: -------------------------------------------------------------------------------- 1 | # expect 2 | 3 | `Expect` is used to used to invoke matchers and give a helpful error message. `Expect` helps a test read well and minimize overhead and scaffolding. 4 | 5 | ```go 6 | func TestSomething(t *testing.T) { 7 | x := "foo" 8 | Expect(t, x).To(Equal("bar")) 9 | } 10 | ``` 11 | 12 | The previous example will fail and pass the error from the matcher `Equal` to `t.Fatal`. 13 | 14 | ```go 15 | func TestSomething(t *testing.T) { 16 | x := "foo" 17 | Expect(t, x).To(StartsWith("foobar")) 18 | } 19 | ``` 20 | 21 | This example will pass. 22 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/expect/expect.go: -------------------------------------------------------------------------------- 1 | package expect 2 | 3 | import ( 4 | "path" 5 | "runtime" 6 | 7 | "github.com/poy/onpar/matchers" 8 | ) 9 | 10 | // ToMatcher is a type that can be passed to (*To).To(). 11 | type ToMatcher interface { 12 | Match(actual interface{}) (resultValue interface{}, err error) 13 | } 14 | 15 | // Differ is a type of matcher that will need to diff its expected and 16 | // actual values. 17 | type DiffMatcher interface { 18 | UseDiffer(matchers.Differ) 19 | } 20 | 21 | // T is a type that we can perform assertions with. 22 | type T interface { 23 | Fatalf(format string, args ...interface{}) 24 | } 25 | 26 | // THelper has the method that tells the testing framework that it can declare 27 | // itself a test helper. 28 | type THelper interface { 29 | Helper() 30 | } 31 | 32 | // Opt is an option that can be passed to New to modify Expectations. 33 | type Opt func(To) To 34 | 35 | // WithDiffer stores the diff.Differ to be used when displaying diffs between 36 | // actual and expected values. 37 | func WithDiffer(d matchers.Differ) Opt { 38 | return func(t To) To { 39 | t.differ = d 40 | return t 41 | } 42 | } 43 | 44 | // Expectation is provided to make it clear what the expect function does. 45 | type Expectation func(actual interface{}) *To 46 | 47 | // New creates a new Expectation 48 | func New(t T, opts ...Opt) Expectation { 49 | return func(actual interface{}) *To { 50 | to := To{ 51 | actual: actual, 52 | t: t, 53 | } 54 | for _, opt := range opts { 55 | to = opt(to) 56 | } 57 | return &to 58 | } 59 | } 60 | 61 | // Expect performs New(t)(actual). 62 | func Expect(t T, actual interface{}) *To { 63 | return New(t)(actual) 64 | } 65 | 66 | // To is a type that stores actual values prior to running them through 67 | // matchers. 68 | type To struct { 69 | actual interface{} 70 | parentErr error 71 | 72 | t T 73 | differ matchers.Differ 74 | } 75 | 76 | // To takes a matcher and passes it the actual value, failing t's T value 77 | // if the matcher returns an error. 78 | func (t *To) To(matcher matchers.Matcher) { 79 | if helper, ok := t.t.(THelper); ok { 80 | helper.Helper() 81 | } 82 | 83 | if d, ok := matcher.(DiffMatcher); ok { 84 | d.UseDiffer(t.differ) 85 | } 86 | 87 | _, err := matcher.Match(t.actual) 88 | if err != nil { 89 | _, fileName, lineNumber, _ := runtime.Caller(1) 90 | t.t.Fatalf("%s\n%s:%d", err.Error(), path.Base(fileName), lineNumber) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/README.md: -------------------------------------------------------------------------------- 1 | # OnPar Matchers 2 | 3 | OnPar provides a set of minimalistic matchers to get you started. 4 | However, the intention is to be able to write your own custom matchers so that 5 | your code is more readable. 6 | 7 | 8 | ## Matchers List 9 | - [String Matchers](#string-matchers) 10 | - [Logical Matchers](#logical-matchers) 11 | - [Error Matchers](#error-matchers) 12 | - [Channel Matchers](#channel-matchers) 13 | - [Collection Matchers](#collection-matchers) 14 | - [Other Matchers](#other-matchers) 15 | 16 | 17 | ## String Matchers 18 | ### StartWith 19 | StartWithMatcher accepts a string and succeeds if the actual string starts with 20 | the expected string. 21 | 22 | ```go 23 | Expect(t, "foobar").To(StartWith("foo")) 24 | ``` 25 | 26 | ### EndWith 27 | EndWithMatcher accepts a string and succeeds if the actual string ends with 28 | the expected string. 29 | 30 | ```go 31 | Expect(t, "foobar").To(EndWith("bar")) 32 | ``` 33 | 34 | ### ContainSubstring 35 | ContainSubstringMatcher accepts a string and succeeds if the expected string is a 36 | sub-string of the actual. 37 | 38 | ```go 39 | Expect(t, "foobar").To(ContainSubstring("ooba")) 40 | ``` 41 | ### MatchRegexp 42 | 43 | ## Logical Matchers 44 | ### Not 45 | NotMatcher accepts a matcher and will succeed if the specified matcher fails. 46 | 47 | ```go 48 | Expect(t, false).To(Not(BeTrue())) 49 | ``` 50 | 51 | ### BeAbove 52 | BeAboveMatcher accepts a float64. It succeeds if the actual is greater 53 | than the expected. 54 | 55 | ```go 56 | Expect(t, 100).To(BeAbove(99)) 57 | ``` 58 | 59 | ### BeBelow 60 | BeBelowMatcher accepts a float64. It succeeds if the actual is 61 | less than the expected. 62 | 63 | ```go 64 | Expect(t, 100).To(BeBelow(101)) 65 | ``` 66 | 67 | ### BeFalse 68 | BeFalseMatcher will succeed if actual is false. 69 | 70 | ```go 71 | Expect(t, 2 == 3).To(BeFalse()) 72 | ``` 73 | 74 | ### BeTrue 75 | BeTrueMatcher will succeed if actual is true. 76 | 77 | ```go 78 | Expect(t, 2 == 2).To(BeTrue()) 79 | ``` 80 | 81 | ### Equal 82 | EqualMatcher performs a DeepEqual between the actual and expected. 83 | 84 | ```go 85 | Expect(t, 42).To(Equal(42)) 86 | ``` 87 | 88 | ## Error Matchers 89 | ### HaveOccurred 90 | HaveOccurredMatcher will succeed if the actual value is a non-nil error. 91 | 92 | ```go 93 | Expect(t, err).To(HaveOccurred()) 94 | 95 | Expect(t, nil).To(Not(HaveOccurred())) 96 | ``` 97 | 98 | ## Channel Matchers 99 | ### Receive 100 | ReceiveMatcher only accepts a readable channel. It will error for anything else. 101 | It will attempt to receive from the channel but will not block. 102 | It fails if the channel is closed. 103 | 104 | ```go 105 | c := make(chan bool, 1) 106 | c <- true 107 | Expect(t, c).To(Receive()) 108 | ``` 109 | 110 | ### BeClosed 111 | BeClosedMatcher only accepts a readable channel. It will error for anything else. 112 | It will succeed if the channel is closed. 113 | 114 | ```go 115 | c := make(chan bool) 116 | close(c) 117 | Expect(t, c).To(BeClosed()) 118 | ``` 119 | 120 | ## Collection Matchers 121 | ### HaveCap 122 | This matcher works on Slices, Arrays, Maps and Channels and will succeed if the 123 | type has the specified capacity. 124 | 125 | ```go 126 | Expect(t, []string{"foo", "bar"}).To(HaveCap(2)) 127 | ``` 128 | ### HaveKey 129 | HaveKeyMatcher accepts map types and will succeed if the map contains the 130 | specified key. 131 | 132 | ```go 133 | Expect(t, fooMap).To(HaveKey("foo")) 134 | ``` 135 | 136 | ### HaveLen 137 | HaveLenMatcher accepts Strings, Slices, Arrays, Maps and Channels. It will 138 | succeed if the type has the specified length. 139 | 140 | ```go 141 | Expect(t, "12345").To(HaveLen(5)) 142 | ``` 143 | 144 | ## Other Matchers 145 | ### Always 146 | AlwaysMatcher matches by polling the child matcher until it returns an error. 147 | It will return an error the first time the child matcher returns an error. 148 | If the child matcher never returns an error, then it will return a nil. 149 | 150 | By default, the duration is 100ms with an interval of 10ms. 151 | 152 | ```go 153 | isTrue := func() bool { 154 | return true 155 | } 156 | Expect(t, isTrue).To(Always(BeTrue())) 157 | ``` 158 | 159 | ### Chain 160 | ### ViaPolling 161 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/always.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // AlwaysMatcher matches by polling the child matcher until it returns an error. 8 | // It will return an error the first time the child matcher returns an 9 | // error. If the child matcher never returns an error, 10 | // then it will return a nil. 11 | // 12 | // Duration is the longest scenario for the matcher 13 | // if the child matcher continues to return nil 14 | // 15 | // Interval is the period between polling. 16 | type AlwaysMatcher struct { 17 | Matcher Matcher 18 | Duration, Interval time.Duration 19 | } 20 | 21 | // Always returns a default AlwaysMatcher. Length of 100ms and rate 10ms 22 | func Always(m Matcher) AlwaysMatcher { 23 | return AlwaysMatcher{ 24 | Matcher: m, 25 | } 26 | } 27 | 28 | // Match takes a value that can change over time. Therefore, the only 29 | // two valid options are a function with no arguments and a single return 30 | // type, or a readable channel. Anything else will return an error. 31 | // 32 | // If actual is a channel, then the child matcher will have to handle 33 | // reading from the channel. 34 | // 35 | // If the actual is a function, then the matcher will invoke the value 36 | // and pass the returned value to the child matcher. 37 | func (m AlwaysMatcher) Match(actual interface{}) (interface{}, error) { 38 | if m.Duration == 0 { 39 | m.Duration = 100 * time.Millisecond 40 | } 41 | 42 | if m.Interval == 0 { 43 | m.Interval = 10 * time.Millisecond 44 | } 45 | 46 | f, err := fetchFunc(actual) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | var value interface{} 52 | for i := 0; i < int(m.Duration/m.Interval); i++ { 53 | value, err = m.Matcher.Match(f()) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | time.Sleep(m.Interval) 59 | } 60 | 61 | return value, nil 62 | } 63 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/and.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | type AndMatcher struct { 4 | Children []Matcher 5 | } 6 | 7 | func And(a, b Matcher, ms ...Matcher) AndMatcher { 8 | return AndMatcher{ 9 | Children: append(append([]Matcher{a}, b), ms...), 10 | } 11 | } 12 | 13 | func (m AndMatcher) Match(actual interface{}) (interface{}, error) { 14 | var err error 15 | for _, child := range m.Children { 16 | _, err = child.Match(actual) 17 | if err != nil { 18 | return nil, err 19 | } 20 | } 21 | return actual, nil 22 | } 23 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_above.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // BeAboveMatcher accepts a float64. It succeeds if the 6 | // actual is greater than the expected. 7 | type BeAboveMatcher struct { 8 | expected float64 9 | } 10 | 11 | // BeAbove returns a BeAboveMatcher with the expected value. 12 | func BeAbove(expected float64) BeAboveMatcher { 13 | return BeAboveMatcher{ 14 | expected: expected, 15 | } 16 | } 17 | 18 | func (m BeAboveMatcher) Match(actual interface{}) (interface{}, error) { 19 | f, err := m.toFloat(actual) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | if f <= m.expected { 25 | return nil, fmt.Errorf("%v is not above %f", actual, m.expected) 26 | } 27 | 28 | return actual, nil 29 | } 30 | 31 | func (m BeAboveMatcher) toFloat(actual interface{}) (float64, error) { 32 | switch x := actual.(type) { 33 | case int: 34 | return float64(x), nil 35 | case int32: 36 | return float64(x), nil 37 | case int64: 38 | return float64(x), nil 39 | case float32: 40 | return float64(x), nil 41 | case float64: 42 | return x, nil 43 | default: 44 | return 0, fmt.Errorf("Unsupported type %T", actual) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_below.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // BeBelowMatcher accepts a float64. It succeeds if the actual is 6 | // less than the expected. 7 | type BeBelowMatcher struct { 8 | expected float64 9 | } 10 | 11 | // BeBelow returns a BeBelowMatcher with the expected value. 12 | func BeBelow(expected float64) BeBelowMatcher { 13 | return BeBelowMatcher{ 14 | expected: expected, 15 | } 16 | } 17 | 18 | func (m BeBelowMatcher) Match(actual interface{}) (interface{}, error) { 19 | f, err := m.toFloat(actual) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | if f >= m.expected { 25 | return nil, fmt.Errorf("%v is not below %f", actual, m.expected) 26 | } 27 | 28 | return actual, nil 29 | } 30 | 31 | func (m BeBelowMatcher) toFloat(actual interface{}) (float64, error) { 32 | switch x := actual.(type) { 33 | case int: 34 | return float64(x), nil 35 | case int32: 36 | return float64(x), nil 37 | case int64: 38 | return float64(x), nil 39 | case float32: 40 | return float64(x), nil 41 | case float64: 42 | return x, nil 43 | default: 44 | return 0, fmt.Errorf("Unsupported type %T", actual) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_closed.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // BeClosedMatcher only accepts a readable channel. 9 | // It will error for anything else. 10 | // It will succeed if the channel is closed. 11 | type BeClosedMatcher struct{} 12 | 13 | // BeClosed returns a BeClosedMatcher 14 | func BeClosed() BeClosedMatcher { 15 | return BeClosedMatcher{} 16 | } 17 | 18 | func (m BeClosedMatcher) Match(actual interface{}) (interface{}, error) { 19 | t := reflect.TypeOf(actual) 20 | if t.Kind() != reflect.Chan || t.ChanDir() == reflect.SendDir { 21 | return nil, fmt.Errorf("%s is not a readable channel", t.String()) 22 | } 23 | 24 | v := reflect.ValueOf(actual) 25 | 26 | winnerIndex, _, open := reflect.Select([]reflect.SelectCase{ 27 | reflect.SelectCase{Dir: reflect.SelectRecv, Chan: v}, 28 | reflect.SelectCase{Dir: reflect.SelectDefault}, 29 | }) 30 | 31 | if winnerIndex == 0 && !open { 32 | return actual, nil 33 | } 34 | 35 | return nil, fmt.Errorf("channel open") 36 | } 37 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_false.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // BeFalseMatcher will succeed if actual is false. 6 | type BeFalseMatcher struct{} 7 | 8 | // BeFalse will return a BeFalseMatcher 9 | func BeFalse() BeFalseMatcher { 10 | return BeFalseMatcher{} 11 | } 12 | 13 | func (m BeFalseMatcher) Match(actual interface{}) (interface{}, error) { 14 | f, ok := actual.(bool) 15 | if !ok { 16 | return nil, fmt.Errorf("'%v' (%[1]T) is not a bool", actual) 17 | } 18 | 19 | if f { 20 | return nil, fmt.Errorf("%t is not false", actual) 21 | } 22 | 23 | return actual, nil 24 | } 25 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_nil.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // IsNilMatcher will succeed if actual is nil. 9 | type IsNilMatcher struct{} 10 | 11 | // IsNil will return a IsNilMatcher. 12 | func IsNil() IsNilMatcher { 13 | return IsNilMatcher{} 14 | } 15 | 16 | func (m IsNilMatcher) Match(actual interface{}) (interface{}, error) { 17 | if actual == nil { 18 | return nil, nil 19 | } 20 | 21 | var isNil bool 22 | switch reflect.TypeOf(actual).Kind() { 23 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 24 | isNil = reflect.ValueOf(actual).IsNil() 25 | } 26 | 27 | if isNil { 28 | return nil, nil 29 | } 30 | 31 | return actual, fmt.Errorf("%v is not nil", actual) 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/be_true.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // BeTrueMatcher will succeed if actual is true. 6 | type BeTrueMatcher struct{} 7 | 8 | // BeTrue will return a BeTrueMatcher 9 | func BeTrue() BeTrueMatcher { 10 | return BeTrueMatcher{} 11 | } 12 | 13 | func (m BeTrueMatcher) Match(actual interface{}) (interface{}, error) { 14 | f, ok := actual.(bool) 15 | if !ok { 16 | return nil, fmt.Errorf("'%v' (%[1]T) is not a bool", actual) 17 | } 18 | 19 | if !f { 20 | return nil, fmt.Errorf("%t is not true", actual) 21 | } 22 | return actual, nil 23 | } 24 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/chain.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | type ChainMatcher struct { 4 | Children []Matcher 5 | } 6 | 7 | func Chain(a, b Matcher, ms ...Matcher) ChainMatcher { 8 | return ChainMatcher{ 9 | Children: append(append([]Matcher{a}, b), ms...), 10 | } 11 | } 12 | 13 | func (m ChainMatcher) Match(actual interface{}) (interface{}, error) { 14 | var err error 15 | next := actual 16 | for _, child := range m.Children { 17 | next, err = child.Match(next) 18 | if err != nil { 19 | return nil, err 20 | } 21 | } 22 | return next, nil 23 | } 24 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/contain.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type ContainMatcher struct { 9 | values []interface{} 10 | } 11 | 12 | func Contain(values ...interface{}) ContainMatcher { 13 | return ContainMatcher{ 14 | values: values, 15 | } 16 | } 17 | 18 | func (m ContainMatcher) Match(actual interface{}) (interface{}, error) { 19 | actualType := reflect.TypeOf(actual) 20 | if actualType.Kind() != reflect.Slice && actualType.Kind() != reflect.Array { 21 | return nil, fmt.Errorf("%s is not a Slice or Array", actualType.Kind()) 22 | } 23 | 24 | actualValue := reflect.ValueOf(actual) 25 | for _, elem := range m.values { 26 | if !m.containsElem(actualValue, elem) { 27 | return nil, fmt.Errorf("%v does not contain %v", actual, elem) 28 | } 29 | } 30 | 31 | return actual, nil 32 | } 33 | 34 | func (m ContainMatcher) containsElem(actual reflect.Value, elem interface{}) bool { 35 | for i := 0; i < actual.Len(); i++ { 36 | if reflect.DeepEqual(actual.Index(i).Interface(), elem) { 37 | return true 38 | } 39 | } 40 | 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/contain_sub_string.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // ContainSubstringMatcher accepts a string and succeeds 9 | // if the actual string contains the expected string. 10 | type ContainSubstringMatcher struct { 11 | substr string 12 | } 13 | 14 | // ContainSubstring returns a ContainSubstringMatcher with the 15 | // expected substring. 16 | func ContainSubstring(substr string) ContainSubstringMatcher { 17 | return ContainSubstringMatcher{ 18 | substr: substr, 19 | } 20 | } 21 | 22 | func (m ContainSubstringMatcher) Match(actual interface{}) (interface{}, error) { 23 | s, ok := actual.(string) 24 | if !ok { 25 | return nil, fmt.Errorf("'%v' (%T) is not a string", actual, actual) 26 | } 27 | 28 | if !strings.Contains(s, m.substr) { 29 | return nil, fmt.Errorf("%s does not contain %s", s, m.substr) 30 | } 31 | 32 | return actual, nil 33 | } 34 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/differ.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | // Differ is a type that can show a detailed difference between an 4 | // actual and an expected value. 5 | type Differ interface { 6 | Diff(actual, expected interface{}) string 7 | } 8 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/end_with.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // EndWithMatcher accepts a string and succeeds 9 | // if the actual string ends with the expected string. 10 | type EndWithMatcher struct { 11 | suffix string 12 | } 13 | 14 | // EndWith returns an EndWithMatcher with the expected suffix. 15 | func EndWith(suffix string) EndWithMatcher { 16 | return EndWithMatcher{ 17 | suffix: suffix, 18 | } 19 | } 20 | 21 | func (m EndWithMatcher) Match(actual interface{}) (interface{}, error) { 22 | s, ok := actual.(string) 23 | if !ok { 24 | return nil, fmt.Errorf("'%v' (%T) is not a string", actual, actual) 25 | } 26 | 27 | if !strings.HasSuffix(s, m.suffix) { 28 | return nil, fmt.Errorf("%s does not end with %s", s, m.suffix) 29 | } 30 | 31 | return actual, nil 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/equal.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // EqualMatcher performs a DeepEqual between the actual and expected. 9 | type EqualMatcher struct { 10 | expected interface{} 11 | differ Differ 12 | } 13 | 14 | // Equal returns an EqualMatcher with the expected value 15 | func Equal(expected interface{}) *EqualMatcher { 16 | return &EqualMatcher{ 17 | expected: expected, 18 | } 19 | } 20 | 21 | func (m *EqualMatcher) UseDiffer(d Differ) { 22 | m.differ = d 23 | } 24 | 25 | func (m EqualMatcher) Match(actual interface{}) (interface{}, error) { 26 | if !reflect.DeepEqual(actual, m.expected) { 27 | if m.differ == nil { 28 | return nil, fmt.Errorf("%+v (%[1]T) to equal %+v (%[2]T)", actual, m.expected) 29 | } 30 | return nil, fmt.Errorf("expected %v to equal %v\ndiff: %s", actual, m.expected, m.differ.Diff(actual, m.expected)) 31 | } 32 | 33 | return actual, nil 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/fetch.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type FetchMatcher struct { 9 | OutputTo interface{} 10 | } 11 | 12 | func Fetch(outputTo interface{}) FetchMatcher { 13 | return FetchMatcher{ 14 | OutputTo: outputTo, 15 | } 16 | } 17 | 18 | func (m FetchMatcher) Match(actual interface{}) (interface{}, error) { 19 | outType := reflect.TypeOf(m.OutputTo) 20 | outValue := reflect.ValueOf(m.OutputTo) 21 | actualValue := reflect.ValueOf(actual) 22 | 23 | if outType.Kind() != reflect.Ptr { 24 | return nil, fmt.Errorf("%s is not a pointer type", outType.String()) 25 | } 26 | 27 | if !reflect.TypeOf(actualValue.Interface()).AssignableTo(outType.Elem()) { 28 | return nil, fmt.Errorf("can not assigned %s to %s", 29 | reflect.TypeOf(actualValue.Interface()).String(), 30 | reflect.TypeOf(m.OutputTo).String(), 31 | ) 32 | } 33 | 34 | outValue.Elem().Set(actualValue) 35 | return actual, nil 36 | } 37 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/have_cap.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // This matcher works on Slices, Arrays, Maps and Channels and will succeed if the 9 | // type has the specified capacity. 10 | type HaveCapMatcher struct { 11 | expected int 12 | } 13 | 14 | // HaveCap returns a HaveCapMatcher with the specified capacity 15 | func HaveCap(expected int) HaveCapMatcher { 16 | return HaveCapMatcher{ 17 | expected: expected, 18 | } 19 | } 20 | 21 | func (m HaveCapMatcher) Match(actual interface{}) (interface{}, error) { 22 | var c int 23 | switch reflect.TypeOf(actual).Kind() { 24 | case reflect.Slice, reflect.Array, reflect.Map, reflect.Chan: 25 | c = reflect.ValueOf(actual).Cap() 26 | default: 27 | return nil, fmt.Errorf("'%v' (%T) is not a Slice, Array, Map or Channel", actual, actual) 28 | } 29 | 30 | if c != m.expected { 31 | return nil, fmt.Errorf("%v (cap=%d) does not have a capacity %d", actual, c, m.expected) 32 | } 33 | 34 | return actual, nil 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/have_key.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // HaveKeyMatcher accepts map types and will succeed if the map contains the 9 | // specified key. 10 | type HaveKeyMatcher struct { 11 | key interface{} 12 | } 13 | 14 | // HaveKey returns a HaveKeyMatcher with the specified key. 15 | func HaveKey(key interface{}) HaveKeyMatcher { 16 | return HaveKeyMatcher{ 17 | key: key, 18 | } 19 | } 20 | 21 | func (m HaveKeyMatcher) Match(actual interface{}) (interface{}, error) { 22 | t := reflect.TypeOf(actual) 23 | if t.Kind() != reflect.Map { 24 | return nil, fmt.Errorf("'%v' (%T) is not a Map", actual, actual) 25 | } 26 | 27 | if t.Key() != reflect.TypeOf(m.key) { 28 | return nil, fmt.Errorf("'%v' (%T) has a Key type of %v not %T", actual, actual, t.Key(), m.key) 29 | } 30 | 31 | v := reflect.ValueOf(actual) 32 | value := v.MapIndex(reflect.ValueOf(m.key)) 33 | if !value.IsValid() { 34 | return nil, fmt.Errorf("unable to find key %v in %v", m.key, actual) 35 | } 36 | 37 | return value.Interface(), nil 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/have_len.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // HaveLenMatcher accepts Strings, Slices, Arrays, Maps and Channels. It will 9 | // succeed if the type has the specified length. 10 | type HaveLenMatcher struct { 11 | expected int 12 | } 13 | 14 | // HaveLen returns a HaveLenMatcher with the specified length. 15 | func HaveLen(expected int) HaveLenMatcher { 16 | return HaveLenMatcher{ 17 | expected: expected, 18 | } 19 | } 20 | 21 | func (m HaveLenMatcher) Match(actual interface{}) (interface{}, error) { 22 | var l int 23 | switch reflect.TypeOf(actual).Kind() { 24 | case reflect.Slice, reflect.Array, reflect.Map, reflect.String, reflect.Chan: 25 | l = reflect.ValueOf(actual).Len() 26 | default: 27 | return nil, fmt.Errorf("'%v' (%T) is not a Slice, Array, Map, String or Channel", actual, actual) 28 | } 29 | 30 | if l != m.expected { 31 | return nil, fmt.Errorf("%v (len=%d) does not have a length of %d", actual, l, m.expected) 32 | } 33 | 34 | return actual, nil 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/have_occurred.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // HaveOccurredMatcher will succeed if the actual value is a non-nil error. 6 | type HaveOccurredMatcher struct { 7 | } 8 | 9 | // HaveOccurred returns a HaveOccurredMatcher 10 | func HaveOccurred() HaveOccurredMatcher { 11 | return HaveOccurredMatcher{} 12 | } 13 | 14 | func (m HaveOccurredMatcher) Match(actual interface{}) (interface{}, error) { 15 | e, ok := actual.(error) 16 | if !ok { 17 | return nil, fmt.Errorf("'%v' (%T) is not an error", actual, actual) 18 | } 19 | 20 | if e == nil { 21 | return nil, fmt.Errorf("err to not be nil") 22 | } 23 | 24 | return nil, nil 25 | } 26 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/is_nil.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // BeNilMatcher will succeed if actual is nil. 9 | type BeNilMatcher struct{} 10 | 11 | // BeNil will return a BeNilMatcher. 12 | func BeNil() BeNilMatcher { 13 | return BeNilMatcher{} 14 | } 15 | 16 | func (m BeNilMatcher) Match(actual interface{}) (interface{}, error) { 17 | if actual == nil { 18 | return nil, nil 19 | } 20 | 21 | var isNil bool 22 | switch reflect.TypeOf(actual).Kind() { 23 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 24 | isNil = reflect.ValueOf(actual).IsNil() 25 | } 26 | 27 | if isNil { 28 | return nil, nil 29 | } 30 | 31 | return actual, fmt.Errorf("%v is not nil", actual) 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/match_json.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | ) 8 | 9 | // MatchJSONMatcher converts both expected and actual to a map[string]interface{} 10 | // and does a reflect.DeepEqual between them 11 | type MatchJSONMatcher struct { 12 | expected interface{} 13 | } 14 | 15 | // MatchJSON returns an MatchJSONMatcher with the expected value 16 | func MatchJSON(expected interface{}) MatchJSONMatcher { 17 | return MatchJSONMatcher{ 18 | expected: expected, 19 | } 20 | } 21 | 22 | func (m MatchJSONMatcher) Match(actual interface{}) (interface{}, error) { 23 | a, sa, err := m.unmarshal(actual) 24 | if err != nil { 25 | return nil, fmt.Errorf("Error with %s: %s", sa, err) 26 | } 27 | 28 | e, se, err := m.unmarshal(m.expected) 29 | if err != nil { 30 | return nil, fmt.Errorf("Error with %s: %s", se, err) 31 | } 32 | 33 | if !reflect.DeepEqual(a, e) { 34 | return nil, fmt.Errorf("expected %s to equal %s", sa, se) 35 | } 36 | 37 | return actual, nil 38 | } 39 | 40 | func (m MatchJSONMatcher) unmarshal(x interface{}) (interface{}, string, error) { 41 | var result interface{} 42 | var s string 43 | 44 | switch x := x.(type) { 45 | case []byte: 46 | if err := json.Unmarshal(x, &result); err != nil { 47 | return nil, string(x), err 48 | } 49 | s = string(x) 50 | 51 | case string: 52 | if err := json.Unmarshal([]byte(x), &result); err != nil { 53 | return nil, x, err 54 | } 55 | s = x 56 | 57 | case *string: 58 | if x == nil { 59 | return nil, "", fmt.Errorf("*string cannot be nil") 60 | } 61 | s = *x 62 | if err := json.Unmarshal([]byte(s), &result); err != nil { 63 | return nil, s, err 64 | } 65 | 66 | default: 67 | return nil, "", fmt.Errorf("must be a []byte, *string, or string") 68 | } 69 | 70 | return result, s, nil 71 | } 72 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/match_regexp.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | type MatchRegexpMatcher struct { 9 | pattern string 10 | } 11 | 12 | func MatchRegexp(pattern string) MatchRegexpMatcher { 13 | return MatchRegexpMatcher{ 14 | pattern: pattern, 15 | } 16 | } 17 | 18 | func (m MatchRegexpMatcher) Match(actual interface{}) (interface{}, error) { 19 | r, err := regexp.Compile(m.pattern) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | s, ok := actual.(string) 25 | if !ok { 26 | return nil, fmt.Errorf("'%v' (%T) is not a string", actual, actual) 27 | } 28 | 29 | if !r.MatchString(s) { 30 | return nil, fmt.Errorf("%s does not match pattern %s", s, m.pattern) 31 | } 32 | 33 | return actual, nil 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/not.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "fmt" 4 | 5 | // Matcher is a type that matches expected against actuals. 6 | type Matcher interface { 7 | Match(actual interface{}) (resultValue interface{}, err error) 8 | } 9 | 10 | // NotMatcher accepts a matcher and will succeed if the specified matcher fails. 11 | type NotMatcher struct { 12 | child Matcher 13 | } 14 | 15 | // Not returns a NotMatcher with the specified child matcher. 16 | func Not(child Matcher) NotMatcher { 17 | return NotMatcher{ 18 | child: child, 19 | } 20 | } 21 | 22 | func (m NotMatcher) Match(actual interface{}) (interface{}, error) { 23 | v, err := m.child.Match(actual) 24 | if err == nil { 25 | return nil, fmt.Errorf("%+v (%[1]T) was expected to fail matcher %#v", actual, m.child) 26 | } 27 | 28 | return v, nil 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/or.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | type OrMatcher struct { 4 | Children []Matcher 5 | } 6 | 7 | func Or(a, b Matcher, ms ...Matcher) OrMatcher { 8 | return OrMatcher{ 9 | Children: append(append([]Matcher{a}, b), ms...), 10 | } 11 | } 12 | 13 | func (m OrMatcher) Match(actual interface{}) (interface{}, error) { 14 | var err error 15 | for _, child := range m.Children { 16 | _, err = child.Match(actual) 17 | if err == nil { 18 | return actual, nil 19 | } 20 | } 21 | return nil, err 22 | } 23 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/panic.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import "errors" 4 | 5 | // PanicMatcher accepts a function. It succeeds if the function panics. 6 | type PanicMatcher struct { 7 | } 8 | 9 | // Panic returns a Panic matcher. 10 | func Panic() PanicMatcher { 11 | return PanicMatcher{} 12 | } 13 | 14 | func (m PanicMatcher) Match(actual interface{}) (result interface{}, err error) { 15 | f, ok := actual.(func()) 16 | if !ok { 17 | return nil, errors.New("actual must be a func()") 18 | } 19 | 20 | defer func() { 21 | r := recover() 22 | if r == nil { 23 | err = errors.New("expected to panic") 24 | } 25 | }() 26 | 27 | f() 28 | 29 | return nil, nil 30 | } 31 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/receive.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | // ReceiveOpt is an option that can be passed to the 10 | // ReceiveMatcher constructor. 11 | type ReceiveOpt func(ReceiveMatcher) ReceiveMatcher 12 | 13 | // ReceiveWait is an option that makes the ReceiveMatcher 14 | // wait for values for the provided duration before 15 | // deciding that the channel failed to receive. 16 | func ReceiveWait(t time.Duration) ReceiveOpt { 17 | return func(m ReceiveMatcher) ReceiveMatcher { 18 | m.timeout = t 19 | return m 20 | } 21 | } 22 | 23 | // ReceiveMatcher only accepts a readable channel. It will error for anything else. 24 | // It will attempt to receive from the channel but will not block. 25 | // It fails if the channel is closed. 26 | type ReceiveMatcher struct { 27 | timeout time.Duration 28 | } 29 | 30 | // Receive will return a ReceiveMatcher 31 | func Receive(opts ...ReceiveOpt) ReceiveMatcher { 32 | var m ReceiveMatcher 33 | for _, opt := range opts { 34 | m = opt(m) 35 | } 36 | return m 37 | } 38 | 39 | func (m ReceiveMatcher) Match(actual interface{}) (interface{}, error) { 40 | t := reflect.TypeOf(actual) 41 | if t.Kind() != reflect.Chan || t.ChanDir() == reflect.SendDir { 42 | return nil, fmt.Errorf("%s is not a readable channel", t.String()) 43 | } 44 | 45 | timeout := reflect.SelectCase{ 46 | Dir: reflect.SelectDefault, 47 | } 48 | if m.timeout != 0 { 49 | timeout.Dir = reflect.SelectRecv 50 | timeout.Chan = reflect.ValueOf(time.After(m.timeout)) 51 | } 52 | i, v, ok := reflect.Select([]reflect.SelectCase{ 53 | {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(actual)}, 54 | timeout, 55 | }) 56 | if i == 1 || !ok { 57 | return nil, fmt.Errorf("did not receive") 58 | } 59 | 60 | return v.Interface(), nil 61 | } 62 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/start_with.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // StartWithMatcher accepts a string and succeeds 9 | // if the actual string starts with the expected string. 10 | type StartWithMatcher struct { 11 | prefix string 12 | } 13 | 14 | // StartWith returns a StartWithMatcher with the expected prefix. 15 | func StartWith(prefix string) StartWithMatcher { 16 | return StartWithMatcher{ 17 | prefix: prefix, 18 | } 19 | } 20 | 21 | func (m StartWithMatcher) Match(actual interface{}) (interface{}, error) { 22 | s, ok := actual.(string) 23 | if !ok { 24 | return nil, fmt.Errorf("'%v' (%T) is not a string", actual, actual) 25 | } 26 | 27 | if !strings.HasPrefix(s, m.prefix) { 28 | return nil, fmt.Errorf("%s does not start with %s", s, m.prefix) 29 | } 30 | 31 | return actual, nil 32 | } 33 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/matchers/via_polling.go: -------------------------------------------------------------------------------- 1 | package matchers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | ) 8 | 9 | // ViaPollingMatcher matches by polling the child matcher until 10 | // it returns a success. It will return success the first time 11 | // the child matcher returns a success. If the child matcher 12 | // never returns a nil, then it will return the last error. 13 | // 14 | // Duration is the worst case scenario for the matcher 15 | // if the child matcher continues to return an error 16 | // 17 | // Interval is the period between polling. 18 | type ViaPollingMatcher struct { 19 | Matcher Matcher 20 | Duration, Interval time.Duration 21 | } 22 | 23 | // ViaPolling returns the default ViaPollingMatcher. Length of 1s 24 | // and Rate of 10ms 25 | func ViaPolling(m Matcher) ViaPollingMatcher { 26 | return ViaPollingMatcher{ 27 | Matcher: m, 28 | } 29 | } 30 | 31 | // Match takes a value that can change over time. Therefore, the only 32 | // two valid options are a function with no arguments and a single return 33 | // type, or a readable channel. Anything else will return an error. 34 | // 35 | // If actual is a channel, then the child matcher will have to handle 36 | // reading from the channel. 37 | // 38 | // If the actual is a function, then the matcher will invoke the value 39 | // and pass the returned value to the child matcher. 40 | func (m ViaPollingMatcher) Match(actual interface{}) (interface{}, error) { 41 | if m.Duration == 0 { 42 | m.Duration = time.Second 43 | } 44 | 45 | if m.Interval == 0 { 46 | m.Interval = 10 * time.Millisecond 47 | } 48 | 49 | f, err := fetchFunc(actual) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | var value interface{} 55 | for i := 0; i < int(m.Duration/m.Interval); i++ { 56 | value, err = m.Matcher.Match(f()) 57 | if err == nil { 58 | return value, nil 59 | } 60 | 61 | time.Sleep(m.Interval) 62 | } 63 | 64 | return nil, err 65 | } 66 | 67 | func fetchFunc(actual interface{}) (func() interface{}, error) { 68 | t := reflect.TypeOf(actual) 69 | switch t.Kind() { 70 | case reflect.Func: 71 | return fetchFuncFromFunc(actual) 72 | case reflect.Chan: 73 | return fetchFuncFromChan(actual) 74 | default: 75 | return nil, fmt.Errorf("invalid type: %v", t) 76 | } 77 | } 78 | 79 | func fetchFuncFromChan(actual interface{}) (func() interface{}, error) { 80 | t := reflect.TypeOf(actual) 81 | if t.ChanDir() == reflect.SendDir { 82 | return nil, fmt.Errorf("channel must be able to receive") 83 | } 84 | 85 | return func() interface{} { 86 | return actual 87 | }, nil 88 | } 89 | 90 | func fetchFuncFromFunc(actual interface{}) (func() interface{}, error) { 91 | t := reflect.TypeOf(actual) 92 | if t.NumIn() != 0 { 93 | return nil, fmt.Errorf("func must not take any arguments") 94 | } 95 | 96 | if t.NumOut() != 1 { 97 | return nil, fmt.Errorf("func must have one return type") 98 | } 99 | 100 | return func() interface{} { 101 | v := reflect.ValueOf(actual) 102 | retValues := v.Call(nil) 103 | return retValues[0].Interface() 104 | }, nil 105 | } 106 | -------------------------------------------------------------------------------- /vendor/github.com/poy/onpar/onpar.go: -------------------------------------------------------------------------------- 1 | package onpar 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | 9 | "github.com/poy/onpar/diff" 10 | ) 11 | 12 | // Opt is an option type to pass to onpar's constructor. 13 | type Opt func(Onpar) Onpar 14 | 15 | // WithCallCount sets a call count to pass to runtime.Caller. 16 | func WithCallCount(count int) Opt { 17 | return func(o Onpar) Onpar { 18 | o.callCount = count 19 | return o 20 | } 21 | } 22 | 23 | // Onpar stores the state of the specs and groups 24 | type Onpar struct { 25 | current *level 26 | callCount int 27 | diffOpts []diff.Opt 28 | } 29 | 30 | // New creates a new Onpar test suite 31 | func New(opts ...Opt) *Onpar { 32 | o := Onpar{ 33 | current: &level{}, 34 | callCount: 1, 35 | } 36 | for _, opt := range opts { 37 | o = opt(o) 38 | } 39 | return &o 40 | } 41 | 42 | // NewWithCallCount is deprecated syntax for New(WithCallCount(count)) 43 | func NewWithCallCount(count int) *Onpar { 44 | return New(WithCallCount(count)) 45 | } 46 | 47 | // Spec is a test that runs in parallel with other specs. The provided function 48 | // takes the `testing.T` for test assertions and any arguments the `BeforeEach()` 49 | // returns. 50 | func (o *Onpar) Spec(name string, f interface{}) { 51 | _, fileName, lineNumber, _ := runtime.Caller(o.callCount) 52 | v := reflect.ValueOf(f) 53 | spec := specInfo{ 54 | name: name, 55 | f: &v, 56 | ft: reflect.TypeOf(f), 57 | fileName: fileName, 58 | lineNumber: lineNumber, 59 | } 60 | o.current.specs = append(o.current.specs, spec) 61 | } 62 | 63 | // Group is used to gather and categorize specs. Each group can have a single 64 | // `BeforeEach()` and `AfterEach()`. 65 | func (o *Onpar) Group(name string, f func()) { 66 | newLevel := &level{ 67 | name: name, 68 | parent: o.current, 69 | } 70 | 71 | o.current.children = append(o.current.children, newLevel) 72 | 73 | oldLevel := o.current 74 | o.current = newLevel 75 | f() 76 | o.current = oldLevel 77 | } 78 | 79 | // BeforeEach is used for any setup that may be required for the specs. 80 | // Each argument returned will be required to be received by following specs. 81 | // Outer BeforeEaches are invoked before inner ones. 82 | func (o *Onpar) BeforeEach(f interface{}) { 83 | if o.current.before != nil { 84 | panic(fmt.Sprintf("Level '%s' already has a registered BeforeEach", o.current.name)) 85 | } 86 | _, fileName, lineNumber, _ := runtime.Caller(o.callCount) 87 | 88 | v := reflect.ValueOf(f) 89 | o.current.before = &specInfo{ 90 | f: &v, 91 | ft: reflect.TypeOf(f), 92 | fileName: fileName, 93 | lineNumber: lineNumber, 94 | } 95 | } 96 | 97 | // AfterEach is used to cleanup anything from the specs or BeforeEaches. 98 | // The function takes arguments the same as specs. Inner AfterEaches are invoked 99 | // before outer ones. 100 | func (o *Onpar) AfterEach(f interface{}) { 101 | if o.current.after != nil { 102 | panic(fmt.Sprintf("Level '%s' already has a registered AfterEach", o.current.name)) 103 | } 104 | 105 | _, fileName, lineNumber, _ := runtime.Caller(o.callCount) 106 | 107 | v := reflect.ValueOf(f) 108 | o.current.after = &specInfo{ 109 | f: &v, 110 | ft: reflect.TypeOf(f), 111 | fileName: fileName, 112 | lineNumber: lineNumber, 113 | } 114 | } 115 | 116 | // Run is used to initiate the tests. 117 | func (o *Onpar) Run(t *testing.T) { 118 | traverse(o.current, func(l *level) { 119 | for _, spec := range l.specs { 120 | spec.invoke(t, l) 121 | } 122 | }) 123 | } 124 | 125 | type level struct { 126 | before, after *specInfo 127 | name string 128 | specs []specInfo 129 | 130 | children []*level 131 | parent *level 132 | 133 | beforeEachArgs []reflect.Value 134 | } 135 | 136 | type specInfo struct { 137 | name string 138 | f *reflect.Value 139 | ft reflect.Type 140 | 141 | fileName string 142 | lineNumber int 143 | } 144 | 145 | func (s specInfo) invoke(t *testing.T, l *level) { 146 | desc := buildDesc(l, s) 147 | t.Run(desc, func(tt *testing.T) { 148 | tt.Parallel() 149 | 150 | args, levelArgs := invokeBeforeEach(tt, l) 151 | defer invokeAfterEach(tt, l, levelArgs) 152 | 153 | verifySpecCall(s, args) 154 | 155 | s.f.Call(args) 156 | }) 157 | } 158 | 159 | func verifySpecCall(s specInfo, args []reflect.Value) { 160 | if s.ft.NumOut() != 0 { 161 | panic("Spec functions must not return anything") 162 | } 163 | 164 | verifyCall("Spec", s, args) 165 | } 166 | 167 | func verifyCall(name string, s specInfo, args []reflect.Value) { 168 | argStr := buildReadableArgs(args) 169 | 170 | if s.ft.NumIn() != len(args) { 171 | panic( 172 | fmt.Sprintf("Invalid number of args (%d): expected %s func (%s:%d) to take arguments: %v", 173 | s.ft.NumIn(), name, s.fileName, s.lineNumber, argStr), 174 | ) 175 | } 176 | 177 | for i := 0; i < s.ft.NumIn(); i++ { 178 | if s.ft.In(i) != args[i].Type() { 179 | panic( 180 | fmt.Sprintf("Invaid arg type (%s is not %s): expected %s func (%s:%d) to take arguments: %v", 181 | s.ft.In(i).String(), args[i].Type(), name, s.fileName, s.lineNumber, argStr), 182 | ) 183 | } 184 | } 185 | } 186 | 187 | func buildReadableArgs(args []reflect.Value) string { 188 | if len(args) == 0 { 189 | return "" 190 | } 191 | 192 | var result string 193 | for _, arg := range args { 194 | result = fmt.Sprintf("%s, %s", result, arg.Type().String()) 195 | } 196 | return result[1:] 197 | } 198 | 199 | func invokeBeforeEach(tt *testing.T, l *level) ([]reflect.Value, map[*level][]reflect.Value) { 200 | args := []reflect.Value{ 201 | reflect.ValueOf(tt), 202 | } 203 | levelArgs := make(map[*level][]reflect.Value) 204 | 205 | type beforeEachInfo struct { 206 | s *specInfo 207 | l *level 208 | } 209 | var beforeEaches []beforeEachInfo 210 | 211 | rTraverse(l, func(ll *level) { 212 | beforeEaches = append(beforeEaches, beforeEachInfo{ 213 | s: ll.before, 214 | l: ll, 215 | }) 216 | }) 217 | 218 | for i := len(beforeEaches) - 1; i >= 0; i-- { 219 | be := beforeEaches[i] 220 | 221 | if be.s != nil { 222 | verifyCall("BeforeEach", *be.s, args) 223 | args = be.s.f.Call(args) 224 | } 225 | 226 | levelArgs[be.l] = args 227 | } 228 | 229 | return args, levelArgs 230 | } 231 | 232 | func invokeAfterEach(tt *testing.T, l *level, levelArgs map[*level][]reflect.Value) { 233 | rTraverse(l, func(ll *level) { 234 | beforeEachArgs := levelArgs[ll] 235 | if beforeEachArgs == nil { 236 | beforeEachArgs = []reflect.Value{ 237 | reflect.ValueOf(tt), 238 | } 239 | } 240 | 241 | if ll.after != nil { 242 | verifyCall("AfterEach", *ll.after, beforeEachArgs) 243 | ll.after.f.Call(beforeEachArgs) 244 | } 245 | }) 246 | } 247 | 248 | func buildDesc(l *level, i specInfo) string { 249 | desc := i.name 250 | rTraverse(l, func(ll *level) { 251 | if ll.name == "" { 252 | return 253 | } 254 | desc = fmt.Sprintf("%s/%s", ll.name, desc) 255 | }) 256 | 257 | return desc 258 | } 259 | 260 | func traverse(l *level, f func(*level)) { 261 | if l == nil { 262 | return 263 | } 264 | 265 | f(l) 266 | 267 | for _, child := range l.children { 268 | traverse(child, f) 269 | } 270 | } 271 | 272 | func rTraverse(l *level, f func(*level)) { 273 | if l == nil { 274 | return 275 | } 276 | 277 | f(l) 278 | 279 | rTraverse(l.parent, f) 280 | } 281 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/poy/onpar v1.1.2 2 | ## explicit; go 1.14 3 | github.com/poy/onpar 4 | github.com/poy/onpar/diff 5 | github.com/poy/onpar/expect 6 | github.com/poy/onpar/matchers 7 | --------------------------------------------------------------------------------