@shortlink-org/portolan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"go/types"
|
|
6
|
-
"sort"
|
|
7
|
-
"strings"
|
|
8
|
-
|
|
9
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
// extractTransport reads the http layer and answers with which endpoints run
|
|
13
|
-
// which use case, keyed by "<aggregate>/<use case>".
|
|
14
|
-
//
|
|
15
|
-
// Three things in a handler say it, and they agree:
|
|
16
|
-
//
|
|
17
|
-
// // RegisterUser implements POST /v1/users.
|
|
18
|
-
// func (h *Users) RegisterUser(ctx context.Context, request gen.RegisterUserRequestObject) ... {
|
|
19
|
-
// out, err := h.register.Handle(ctx, dto.Input{...})
|
|
20
|
-
//
|
|
21
|
-
// The method name is the generated server's, which is the document's
|
|
22
|
-
// operationId with a capital letter; `register` is a field of the handler
|
|
23
|
-
// struct, declared as *register.UseCase; and the import that names it says
|
|
24
|
-
// which aggregate's use case that is. None of it needs a type checker, and
|
|
25
|
-
// none of it needs the OpenAPI document - which is the point, because the
|
|
26
|
-
// document is read by a different extractor that knows nothing about Go.
|
|
27
|
-
func extractTransport(root string, layout sourceLayout, b *plugin.Builder) (map[string][]string, []endpointDecl) {
|
|
28
|
-
out := map[string][]string{}
|
|
29
|
-
var endpoints []endpointDecl
|
|
30
|
-
|
|
31
|
-
for _, dir := range layout.http {
|
|
32
|
-
for _, endpoint := range readTransportPackage(root, dir, layout, isHandler, lowerFirst, b) {
|
|
33
|
-
endpoints = append(endpoints, endpoint)
|
|
34
|
-
for _, useCase := range endpoint.useCases {
|
|
35
|
-
out[useCase] = appendOnce(out[useCase], endpoint.id)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// A gRPC handler is read the same way, and differs in two things: what
|
|
41
|
-
// marks a method as one of the contract's, and what the endpoint is called.
|
|
42
|
-
// An rpc is named the same on both sides - GetQuote is GetQuote - so the
|
|
43
|
-
// method name is the id, and it is the id extract-proto puts in `provides`.
|
|
44
|
-
for _, dir := range layout.grpc {
|
|
45
|
-
for _, endpoint := range readTransportPackage(root, dir, layout, isRpcHandler, sameName, b) {
|
|
46
|
-
endpoints = append(endpoints, endpoint)
|
|
47
|
-
for _, useCase := range endpoint.useCases {
|
|
48
|
-
out[useCase] = appendOnce(out[useCase], endpoint.id)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Both the struct set and the method set are walked as maps, so the order
|
|
54
|
-
// operations arrive in is not the order they were written in. Sorting here
|
|
55
|
-
// is what keeps the fragment byte-identical between runs.
|
|
56
|
-
for useCase := range out {
|
|
57
|
-
sort.Strings(out[useCase])
|
|
58
|
-
}
|
|
59
|
-
sort.Slice(endpoints, func(i, j int) bool { return endpoints[i].id < endpoints[j].id })
|
|
60
|
-
|
|
61
|
-
return out, endpoints
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// readTransportPackage reads one discovered handler package: a struct per
|
|
65
|
-
// server, and the methods of it that answer something.
|
|
66
|
-
func readTransportPackage(root, dir string, layout sourceLayout, handler func(*ast.FuncDecl) bool, id func(string) string, b *plugin.Builder) []endpointDecl {
|
|
67
|
-
var endpoints []endpointDecl
|
|
68
|
-
|
|
69
|
-
pkg, err := parsePkg(root, dir)
|
|
70
|
-
if err != nil {
|
|
71
|
-
return endpoints
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
for name, useCase := range handlerFields(pkg, layout) {
|
|
75
|
-
endpoints = append(endpoints, operationsRunning(pkg, name, useCase, handler, id, b)...)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return endpoints
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// handlerFields maps each handler struct to the use case behind each of its
|
|
82
|
-
// fields: Users{register: *register.UseCase} gives {"Users": {"register":
|
|
83
|
-
// "user/register"}}.
|
|
84
|
-
func handlerFields(pkg *pkg, layouts ...sourceLayout) map[string]map[string]string {
|
|
85
|
-
out := map[string]map[string]string{}
|
|
86
|
-
|
|
87
|
-
for _, file := range pkg.files {
|
|
88
|
-
useCases := useCaseImports(file, layouts...)
|
|
89
|
-
if len(useCases) == 0 {
|
|
90
|
-
continue
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
for _, decl := range file.Decls {
|
|
94
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
95
|
-
if !ok {
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for _, spec := range gen.Specs {
|
|
100
|
-
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
101
|
-
if !ok {
|
|
102
|
-
continue
|
|
103
|
-
}
|
|
104
|
-
structType, ok := typeSpec.Type.(*ast.StructType)
|
|
105
|
-
if !ok || structType.Fields == nil {
|
|
106
|
-
continue
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
fields := map[string]string{}
|
|
110
|
-
for _, field := range structType.Fields.List {
|
|
111
|
-
// The field is what a use case is held in, and its type is
|
|
112
|
-
// what says which one: *register.UseCase.
|
|
113
|
-
selector, _, found := strings.Cut(strings.TrimPrefix(types.ExprString(field.Type), "*"), ".")
|
|
114
|
-
if !found {
|
|
115
|
-
continue
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
useCase, known := useCases[selector]
|
|
119
|
-
if !known {
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
for _, name := range field.Names {
|
|
123
|
-
fields[name.Name] = useCase
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if len(fields) > 0 {
|
|
128
|
-
out[typeSpec.Name.Name] = fields
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return out
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// useCaseImports maps the name a file refers to a use case package by - its
|
|
138
|
-
// alias, or the last segment of its path - to "<aggregate>/<use case>".
|
|
139
|
-
//
|
|
140
|
-
// The aggregate is carried because a use case directory name is only unique
|
|
141
|
-
// within one: two aggregates may each have a `get`, and pairing an endpoint
|
|
142
|
-
// with the wrong one would be worse than pairing it with nothing.
|
|
143
|
-
func useCaseImports(file *ast.File, layouts ...sourceLayout) map[string]string {
|
|
144
|
-
out := map[string]string{}
|
|
145
|
-
|
|
146
|
-
for _, spec := range file.Imports {
|
|
147
|
-
importPath := strings.Trim(spec.Path.Value, `"`)
|
|
148
|
-
|
|
149
|
-
aggregate, useCaseName, found := useCaseImport(importPath)
|
|
150
|
-
if !found {
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
|
-
key := aggregate + "/" + useCaseName
|
|
154
|
-
if len(layouts) > 0 {
|
|
155
|
-
if _, discovered := layouts[0].useCases[key]; !discovered {
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
name := useCaseName
|
|
161
|
-
if spec.Name != nil {
|
|
162
|
-
name = spec.Name.Name
|
|
163
|
-
}
|
|
164
|
-
out[name] = key
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return out
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// endpointDecl is one operation of the generated server: what it is called in
|
|
171
|
-
// the document, the use cases it runs in the order it runs them, and where the
|
|
172
|
-
// method that does it can be read.
|
|
173
|
-
//
|
|
174
|
-
// The order matters twice over. It is what pairs an operation with the use
|
|
175
|
-
// cases the catalog says it exposes, and it is the opening of a flow: an
|
|
176
|
-
// endpoint that validates a token and then changes a password does those two
|
|
177
|
-
// things in that order, and a picture that swapped them would be wrong.
|
|
178
|
-
type endpointDecl struct {
|
|
179
|
-
id string
|
|
180
|
-
useCases []string
|
|
181
|
-
source string
|
|
182
|
-
line int
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// operationsRunning finds the handler methods on a struct and the use cases
|
|
186
|
-
// each one reaches.
|
|
187
|
-
func operationsRunning(pkg *pkg, structName string, fields map[string]string, handler func(*ast.FuncDecl) bool, id func(string) string, b *plugin.Builder) []endpointDecl {
|
|
188
|
-
var out []endpointDecl
|
|
189
|
-
|
|
190
|
-
for name, fn := range pkg.methods(structName) {
|
|
191
|
-
if !handler(fn) {
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
operation := id(name)
|
|
196
|
-
used := useCasesTouched(fn, fields)
|
|
197
|
-
|
|
198
|
-
if len(used) == 0 {
|
|
199
|
-
// An endpoint that reaches no use case is doing the work itself, or
|
|
200
|
-
// doing nothing. Either is worth saying out loud.
|
|
201
|
-
b.Warn(operation, pkg.dir+": "+name+" runs no use case; the operation is not paired with anything")
|
|
202
|
-
|
|
203
|
-
continue
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
source, line := pkg.position(fn.Pos())
|
|
207
|
-
out = append(out, endpointDecl{id: operation, useCases: used, source: source, line: line})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return out
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// isHandler tells a generated-server method from a helper beside it. The
|
|
214
|
-
// generated interface takes a `gen.XxxRequestObject`, and nothing else in these
|
|
215
|
-
// packages does.
|
|
216
|
-
func isHandler(fn *ast.FuncDecl) bool {
|
|
217
|
-
if fn.Type.Params == nil {
|
|
218
|
-
return false
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
for _, param := range fn.Type.Params.List {
|
|
222
|
-
if strings.HasSuffix(types.ExprString(param.Type), "RequestObject") {
|
|
223
|
-
return true
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return false
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// isRpcHandler tells a generated-server method from a helper beside it. A
|
|
231
|
-
// protoc-gen-go-grpc server takes a context and a pointer to the request
|
|
232
|
-
// message and answers with a pointer and an error, and the generated
|
|
233
|
-
// Unimplemented embed is what says the struct is one of those servers at all.
|
|
234
|
-
func isRpcHandler(fn *ast.FuncDecl) bool {
|
|
235
|
-
if fn.Type.Params == nil || fn.Type.Results == nil {
|
|
236
|
-
return false
|
|
237
|
-
}
|
|
238
|
-
if len(fn.Type.Params.List) != 2 || len(fn.Type.Results.List) != 2 {
|
|
239
|
-
return false
|
|
240
|
-
}
|
|
241
|
-
if types.ExprString(fn.Type.Params.List[0].Type) != "context.Context" {
|
|
242
|
-
return false
|
|
243
|
-
}
|
|
244
|
-
if !strings.HasPrefix(types.ExprString(fn.Type.Params.List[1].Type), "*") {
|
|
245
|
-
return false
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return types.ExprString(fn.Type.Results.List[1].Type) == "error" &&
|
|
249
|
-
strings.HasPrefix(types.ExprString(fn.Type.Results.List[0].Type), "*")
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// sameName is the id of an rpc: the method's own name, because the contract
|
|
253
|
-
// spells it the same and extract-proto puts that spelling in `provides`.
|
|
254
|
-
func sameName(name string) string { return name }
|
|
255
|
-
|
|
256
|
-
// useCasesTouched collects the receiver's use case fields a body reaches, in a
|
|
257
|
-
// stable order.
|
|
258
|
-
//
|
|
259
|
-
// Any mention counts, not only a call: a handler that resolves a bearer token
|
|
260
|
-
// through one use case before running another has been served by both, and
|
|
261
|
-
// which of them it happens to call through a helper is not a fact about the
|
|
262
|
-
// endpoint.
|
|
263
|
-
func useCasesTouched(fn *ast.FuncDecl, fields map[string]string) []string {
|
|
264
|
-
receiver := receiverIdent(fn)
|
|
265
|
-
if receiver == "" || fn.Body == nil {
|
|
266
|
-
return nil
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
var out []string
|
|
270
|
-
ast.Inspect(fn.Body, func(node ast.Node) bool {
|
|
271
|
-
selector, ok := node.(*ast.SelectorExpr)
|
|
272
|
-
if !ok {
|
|
273
|
-
return true
|
|
274
|
-
}
|
|
275
|
-
ident, ok := selector.X.(*ast.Ident)
|
|
276
|
-
if !ok || ident.Name != receiver {
|
|
277
|
-
return true
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if useCase, known := fields[selector.Sel.Name]; known {
|
|
281
|
-
out = appendOnce(out, useCase)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return true
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
return out
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
func receiverIdent(fn *ast.FuncDecl) string {
|
|
291
|
-
if fn.Recv == nil || len(fn.Recv.List) == 0 || len(fn.Recv.List[0].Names) == 0 {
|
|
292
|
-
return ""
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
return fn.Recv.List[0].Names[0].Name
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
func appendOnce(list []string, value string) []string {
|
|
299
|
-
for _, existing := range list {
|
|
300
|
-
if existing == value {
|
|
301
|
-
return list
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return append(list, value)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// lowerFirst turns a generated method name back into the operationId it was
|
|
309
|
-
// built from: RegisterUser was registerUser in the document.
|
|
310
|
-
func lowerFirst(name string) string {
|
|
311
|
-
if name == "" {
|
|
312
|
-
return name
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
runes := []rune(name)
|
|
316
|
-
if runes[0] >= 'A' && runes[0] <= 'Z' {
|
|
317
|
-
runes[0] = runes[0] - 'A' + 'a'
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return string(runes)
|
|
321
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strings"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
const handlerSource = `package user
|
|
11
|
-
|
|
12
|
-
import (
|
|
13
|
-
"context"
|
|
14
|
-
|
|
15
|
-
"github.com/example/auth/internal/application/user/usecases/register"
|
|
16
|
-
"github.com/example/auth/internal/application/user/usecases/change_password"
|
|
17
|
-
"github.com/example/auth/internal/application/session/usecases/validate"
|
|
18
|
-
validatedto "github.com/example/auth/internal/application/session/usecases/validate/dto"
|
|
19
|
-
"github.com/example/auth/internal/infrastructure/transport/http/gen"
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
type Users struct {
|
|
23
|
-
register *register.UseCase
|
|
24
|
-
changePassword *change_password.UseCase
|
|
25
|
-
validate *validate.UseCase
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
func (h *Users) RegisterUser(ctx context.Context, request gen.RegisterUserRequestObject) (gen.RegisterUserResponseObject, error) {
|
|
29
|
-
return h.register.Handle(ctx, request.Body)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func (h *Users) ChangePassword(ctx context.Context, request gen.ChangePasswordRequestObject) (gen.ChangePasswordResponseObject, error) {
|
|
33
|
-
current, _ := h.validate.Handle(ctx, validatedto.Input{Token: bearer(request)})
|
|
34
|
-
return h.changePassword.Handle(ctx, current)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// bearer is a helper, not an endpoint: it takes no request object.
|
|
38
|
-
func bearer(request gen.ChangePasswordRequestObject) string { return "" }
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
func exposures(t *testing.T, source string) map[string][]string {
|
|
42
|
-
t.Helper()
|
|
43
|
-
|
|
44
|
-
pkg, err := parseSource("handler.go", source)
|
|
45
|
-
if err != nil {
|
|
46
|
-
t.Fatal(err)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
b := &plugin.Builder{}
|
|
50
|
-
out := map[string][]string{}
|
|
51
|
-
for name, fields := range handlerFields(pkg) {
|
|
52
|
-
for _, endpoint := range operationsRunning(pkg, name, fields, isHandler, lowerFirst, b) {
|
|
53
|
-
for _, useCase := range endpoint.useCases {
|
|
54
|
-
out[useCase] = appendOnce(out[useCase], endpoint.id)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return out
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// The method name is the document's operationId with a capital letter, and the
|
|
63
|
-
// field it reaches is the use case that answers.
|
|
64
|
-
func TestHandlerPairsOperationWithUseCase(t *testing.T) {
|
|
65
|
-
got := exposures(t, handlerSource)
|
|
66
|
-
|
|
67
|
-
if want := "registerUser"; strings.Join(got["user/register"], ",") != want {
|
|
68
|
-
t.Errorf("user/register = %v, want %s", got["user/register"], want)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// One endpoint can run more than one use case: this handler resolves the
|
|
73
|
-
// bearer token itself before changing anything.
|
|
74
|
-
func TestOneEndpointCanRunSeveralUseCases(t *testing.T) {
|
|
75
|
-
got := exposures(t, handlerSource)
|
|
76
|
-
|
|
77
|
-
if want := "changePassword"; strings.Join(got["session/validate"], ",") != want {
|
|
78
|
-
t.Errorf("session/validate = %v, want %s", got["session/validate"], want)
|
|
79
|
-
}
|
|
80
|
-
if want := "changePassword"; strings.Join(got["user/change_password"], ",") != want {
|
|
81
|
-
t.Errorf("user/change_password = %v, want %s", got["user/change_password"], want)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// A use case directory name is unique only inside its aggregate, so the
|
|
86
|
-
// aggregate travels with it: pairing an endpoint with the wrong `get` would be
|
|
87
|
-
// worse than pairing it with nothing.
|
|
88
|
-
func TestUseCaseKeysCarryTheirAggregate(t *testing.T) {
|
|
89
|
-
got := exposures(t, handlerSource)
|
|
90
|
-
|
|
91
|
-
for key := range got {
|
|
92
|
-
if !strings.Contains(key, "/") {
|
|
93
|
-
t.Errorf("key %q does not name an aggregate", key)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if _, wrong := got["validate"]; wrong {
|
|
97
|
-
t.Error("a use case was keyed without its aggregate")
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Everything in these packages that is not a generated handler is a helper,
|
|
102
|
-
// and a helper is not an endpoint.
|
|
103
|
-
func TestHelpersAreNotEndpoints(t *testing.T) {
|
|
104
|
-
got := exposures(t, handlerSource)
|
|
105
|
-
|
|
106
|
-
for useCase, ids := range got {
|
|
107
|
-
for _, id := range ids {
|
|
108
|
-
if id == "bearer" {
|
|
109
|
-
t.Errorf("%s was paired with a helper", useCase)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// A handler reaching no use case is doing the work itself, or doing nothing.
|
|
116
|
-
// Either is worth saying rather than silently pairing with nothing.
|
|
117
|
-
func TestAnEndpointRunningNothingIsReported(t *testing.T) {
|
|
118
|
-
pkg, err := parseSource("handler.go", `package user
|
|
119
|
-
|
|
120
|
-
import (
|
|
121
|
-
"context"
|
|
122
|
-
|
|
123
|
-
"github.com/example/auth/internal/application/user/usecases/register"
|
|
124
|
-
"github.com/example/auth/internal/infrastructure/transport/http/gen"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
type Users struct {
|
|
128
|
-
register *register.UseCase
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
func (h *Users) Healthz(ctx context.Context, request gen.HealthzRequestObject) (gen.HealthzResponseObject, error) {
|
|
132
|
-
return nil, nil
|
|
133
|
-
}
|
|
134
|
-
`)
|
|
135
|
-
if err != nil {
|
|
136
|
-
t.Fatal(err)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
b := &plugin.Builder{}
|
|
140
|
-
operationsRunning(pkg, "Users", handlerFields(pkg)["Users"], isHandler, lowerFirst, b)
|
|
141
|
-
|
|
142
|
-
if len(b.Warnings) != 1 || !strings.Contains(b.Warnings[0].Message, "runs no use case") {
|
|
143
|
-
t.Errorf("diagnostics = %+v", b.Warnings)
|
|
144
|
-
}
|
|
145
|
-
}
|