@shortlink-org/portolan 0.1.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/LICENSE +21 -0
- package/README.md +387 -0
- package/catalog/enum_test.go +46 -0
- package/catalog/model.go +932 -0
- package/catalog/roundtrip_test.go +185 -0
- package/catalog/via_test.go +38 -0
- package/cli/portolan.mjs +368 -0
- package/go.mod +14 -0
- package/go.sum +14 -0
- package/index.html +13 -0
- package/internal/commands/cargo.go +154 -0
- package/internal/commands/commands.go +143 -0
- package/internal/commands/commands_test.go +323 -0
- package/internal/commands/gradle.go +77 -0
- package/internal/commands/justfile.go +82 -0
- package/internal/commands/makefile.go +106 -0
- package/internal/commands/maven.go +93 -0
- package/internal/commands/packagejson.go +159 -0
- package/internal/commands/pyproject.go +169 -0
- package/internal/commands/taskfile.go +101 -0
- package/internal/commands/testdata/estate/.cargo/config.toml +7 -0
- package/internal/commands/testdata/estate/Makefile +33 -0
- package/internal/commands/testdata/estate/Taskfile.yml +21 -0
- package/internal/commands/testdata/estate/build.gradle.kts +21 -0
- package/internal/commands/testdata/estate/justfile +20 -0
- package/internal/commands/testdata/estate/package.json +13 -0
- package/internal/commands/testdata/estate/pom.xml +29 -0
- package/internal/commands/testdata/estate/pyproject.toml +27 -0
- package/internal/commands/testdata/estate/xtask/src/main.rs +27 -0
- package/internal/commands/testdata/golden/commands.json +305 -0
- package/internal/gohttp/analyze.go +2500 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +143 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +36 -0
- package/internal/goscan/tree.go +155 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +62 -0
- package/internal/wsdl/parse.go +920 -0
- package/internal/wsdl/parse_test.go +133 -0
- package/package.json +117 -0
- package/plugin/describe.go +107 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +117 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +1160 -0
- package/plugins/extract-adr/describe.go +19 -0
- package/plugins/extract-adr/describe_test.go +11 -0
- package/plugins/extract-adr/extract.go +153 -0
- package/plugins/extract-adr/extract_test.go +350 -0
- package/plugins/extract-adr/history.go +99 -0
- package/plugins/extract-adr/main.go +65 -0
- package/plugins/extract-adr/options.schema.json +26 -0
- package/plugins/extract-adr/parse.go +642 -0
- package/plugins/extract-adr/parse_test.go +405 -0
- package/plugins/extract-asyncapi/describe.go +19 -0
- package/plugins/extract-asyncapi/describe_test.go +11 -0
- package/plugins/extract-asyncapi/extract.go +315 -0
- package/plugins/extract-asyncapi/extract_test.go +197 -0
- package/plugins/extract-asyncapi/main.go +48 -0
- package/plugins/extract-asyncapi/options.schema.json +26 -0
- package/plugins/extract-asyncapi/spec.go +151 -0
- package/plugins/extract-celery/README.md +169 -0
- package/plugins/extract-celery/extract.py +346 -0
- package/plugins/extract-celery/extract_test.py +192 -0
- package/plugins/extract-celery/main.py +66 -0
- package/plugins/extract-celery/options.py +31 -0
- package/plugins/extract-celery/options.schema.json +31 -0
- package/plugins/extract-celery/producers.py +156 -0
- package/plugins/extract-celery/routes_test.py +106 -0
- package/plugins/extract-commands/describe.go +19 -0
- package/plugins/extract-commands/describe_test.go +11 -0
- package/plugins/extract-commands/extract.go +79 -0
- package/plugins/extract-commands/extract_test.go +92 -0
- package/plugins/extract-commands/main.go +55 -0
- package/plugins/extract-commands/options.schema.json +20 -0
- package/plugins/extract-csr/avro.go +258 -0
- package/plugins/extract-csr/describe.go +19 -0
- package/plugins/extract-csr/describe_test.go +11 -0
- package/plugins/extract-csr/extract.go +338 -0
- package/plugins/extract-csr/extract_test.go +374 -0
- package/plugins/extract-csr/jsonschema.go +343 -0
- package/plugins/extract-csr/lock.go +25 -0
- package/plugins/extract-csr/main.go +84 -0
- package/plugins/extract-csr/options.schema.json +43 -0
- package/plugins/extract-csr/subject.go +102 -0
- package/plugins/extract-django/README.md +386 -0
- package/plugins/extract-django/apps.py +100 -0
- package/plugins/extract-django/auth.py +302 -0
- package/plugins/extract-django/auth_test.py +188 -0
- package/plugins/extract-django/clients.py +121 -0
- package/plugins/extract-django/contracts.py +458 -0
- package/plugins/extract-django/database.py +119 -0
- package/plugins/extract-django/domain.py +318 -0
- package/plugins/extract-django/events.py +86 -0
- package/plugins/extract-django/extract.py +601 -0
- package/plugins/extract-django/extract_test.py +397 -0
- package/plugins/extract-django/flows.py +1278 -0
- package/plugins/extract-django/flows_test.py +338 -0
- package/plugins/extract-django/ids.py +42 -0
- package/plugins/extract-django/ids_test.py +30 -0
- package/plugins/extract-django/lifecycle.py +222 -0
- package/plugins/extract-django/lifecycle_test.py +76 -0
- package/plugins/extract-django/main.py +66 -0
- package/plugins/extract-django/openapi.py +229 -0
- package/plugins/extract-django/openapi_test.py +60 -0
- package/plugins/extract-django/operations.py +96 -0
- package/plugins/extract-django/options.py +61 -0
- package/plugins/extract-django/options.schema.json +126 -0
- package/plugins/extract-django/routing.py +214 -0
- package/plugins/extract-django/routing_test.py +198 -0
- package/plugins/extract-django/serializers.py +558 -0
- package/plugins/extract-django/store.py +434 -0
- package/plugins/extract-django/transport.py +242 -0
- package/plugins/extract-flows/describe.go +19 -0
- package/plugins/extract-flows/describe_test.go +11 -0
- package/plugins/extract-flows/extract.go +91 -0
- package/plugins/extract-flows/main.go +45 -0
- package/plugins/extract-flows/options.schema.json +16 -0
- package/plugins/extract-flows/parse.go +593 -0
- package/plugins/extract-flows/parse_test.go +204 -0
- package/plugins/extract-glossary/describe.go +19 -0
- package/plugins/extract-glossary/describe_test.go +11 -0
- package/plugins/extract-glossary/extract.go +115 -0
- package/plugins/extract-glossary/extract_test.go +220 -0
- package/plugins/extract-glossary/main.go +59 -0
- package/plugins/extract-glossary/options.schema.json +21 -0
- package/plugins/extract-glossary/parse.go +214 -0
- package/plugins/extract-glossary/parse_test.go +203 -0
- package/plugins/extract-go/aggregate.go +214 -0
- package/plugins/extract-go/client.go +409 -0
- package/plugins/extract-go/client_test.go +266 -0
- package/plugins/extract-go/describe.go +19 -0
- package/plugins/extract-go/describe_test.go +11 -0
- package/plugins/extract-go/enum.go +195 -0
- package/plugins/extract-go/enum_test.go +82 -0
- package/plugins/extract-go/event.go +99 -0
- package/plugins/extract-go/extract.go +191 -0
- package/plugins/extract-go/extract_test.go +261 -0
- package/plugins/extract-go/flow.go +1441 -0
- package/plugins/extract-go/flow_test.go +609 -0
- package/plugins/extract-go/httpclient.go +174 -0
- package/plugins/extract-go/httpclient_test.go +296 -0
- package/plugins/extract-go/ids.go +92 -0
- package/plugins/extract-go/layout.go +225 -0
- package/plugins/extract-go/layout_test.go +125 -0
- package/plugins/extract-go/lifecycle.go +324 -0
- package/plugins/extract-go/lifecycle_test.go +108 -0
- package/plugins/extract-go/main.go +79 -0
- package/plugins/extract-go/operation.go +157 -0
- package/plugins/extract-go/options.schema.json +66 -0
- package/plugins/extract-go/source.go +316 -0
- package/plugins/extract-go/transport.go +321 -0
- package/plugins/extract-go/transport_test.go +145 -0
- package/plugins/extract-go/wiring.go +434 -0
- package/plugins/extract-go-nats/describe.go +19 -0
- package/plugins/extract-go-nats/describe_test.go +11 -0
- package/plugins/extract-go-nats/extract.go +177 -0
- package/plugins/extract-go-nats/extract_test.go +389 -0
- package/plugins/extract-go-nats/index.go +379 -0
- package/plugins/extract-go-nats/main.go +42 -0
- package/plugins/extract-go-nats/options.schema.json +21 -0
- package/plugins/extract-go-nats/resolve.go +161 -0
- package/plugins/extract-go-nats/sites.go +224 -0
- package/plugins/extract-graphql/describe.go +19 -0
- package/plugins/extract-graphql/describe_test.go +11 -0
- package/plugins/extract-graphql/extract.go +433 -0
- package/plugins/extract-graphql/extract_test.go +256 -0
- package/plugins/extract-graphql/ids.go +49 -0
- package/plugins/extract-graphql/lex.go +237 -0
- package/plugins/extract-graphql/main.go +51 -0
- package/plugins/extract-graphql/options.schema.json +31 -0
- package/plugins/extract-graphql/parse.go +621 -0
- package/plugins/extract-graphql/parse_test.go +122 -0
- package/plugins/extract-http-clients/describe.go +19 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +705 -0
- package/plugins/extract-http-clients/extract_test.go +1263 -0
- package/plugins/extract-http-clients/main.go +42 -0
- package/plugins/extract-http-clients/options.schema.json +33 -0
- package/plugins/extract-java/README.md +190 -0
- package/plugins/extract-java/build/org/portolan/extract/Catalog.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$2.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Call.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Client.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients$Route.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Clients.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain$Aggregate.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Domain.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events$Found.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Events.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Binding.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Draft.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Frame.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Flows.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Ids.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Json.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle$Move.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Lifecycle.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Operation.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi$Spec.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/OpenApi.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$1.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations$UseCase.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Operations.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Rpc.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto$Service.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Proto.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Project.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source$Unit.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Source.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport$Endpoint.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Transport.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Line.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml$Reader.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Yaml.class +0 -0
- package/plugins/extract-java/options.schema.json +70 -0
- package/plugins/extract-openapi/describe.go +19 -0
- package/plugins/extract-openapi/describe_test.go +11 -0
- package/plugins/extract-openapi/discover.go +243 -0
- package/plugins/extract-openapi/extract.go +526 -0
- package/plugins/extract-openapi/extract_test.go +545 -0
- package/plugins/extract-openapi/main.go +75 -0
- package/plugins/extract-openapi/options.schema.json +62 -0
- package/plugins/extract-openapi/spec.go +350 -0
- package/plugins/extract-project/README.md +22 -0
- package/plugins/extract-project/describe.go +19 -0
- package/plugins/extract-project/describe_test.go +11 -0
- package/plugins/extract-project/extract.go +221 -0
- package/plugins/extract-project/extract_test.go +109 -0
- package/plugins/extract-project/main.go +41 -0
- package/plugins/extract-project/options.schema.json +48 -0
- package/plugins/extract-proto/ast.go +125 -0
- package/plugins/extract-proto/consumes.go +77 -0
- package/plugins/extract-proto/describe.go +19 -0
- package/plugins/extract-proto/extract.go +293 -0
- package/plugins/extract-proto/extract_test.go +459 -0
- package/plugins/extract-proto/ids.go +89 -0
- package/plugins/extract-proto/ids_test.go +57 -0
- package/plugins/extract-proto/lex.go +285 -0
- package/plugins/extract-proto/main.go +100 -0
- package/plugins/extract-proto/module.go +120 -0
- package/plugins/extract-proto/options.schema.json +51 -0
- package/plugins/extract-proto/parse.go +720 -0
- package/plugins/extract-proto/parse_test.go +307 -0
- package/plugins/extract-proto/provides.go +236 -0
- package/plugins/extract-proto/resolve.go +222 -0
- package/plugins/extract-proto/resolve_test.go +119 -0
- package/plugins/extract-python-kafka/README.md +36 -0
- package/plugins/extract-python-kafka/extract.py +211 -0
- package/plugins/extract-python-kafka/extract_test.py +68 -0
- package/plugins/extract-python-kafka/main.py +59 -0
- package/plugins/extract-python-kafka/options.py +29 -0
- package/plugins/extract-python-kafka/options.schema.json +31 -0
- package/plugins/extract-redis/README.md +11 -0
- package/plugins/extract-redis/describe.go +19 -0
- package/plugins/extract-redis/describe_test.go +11 -0
- package/plugins/extract-redis/extract.go +183 -0
- package/plugins/extract-redis/extract_test.go +168 -0
- package/plugins/extract-redis/keyspaces.go +469 -0
- package/plugins/extract-redis/main.go +44 -0
- package/plugins/extract-redis/options.schema.json +30 -0
- package/plugins/extract-river/describe.go +19 -0
- package/plugins/extract-river/describe_test.go +11 -0
- package/plugins/extract-river/extract.go +507 -0
- package/plugins/extract-river/extract_test.go +132 -0
- package/plugins/extract-river/main.go +42 -0
- package/plugins/extract-river/options.schema.json +21 -0
- package/plugins/extract-rust/Cargo.lock +133 -0
- package/plugins/extract-rust/Cargo.toml +23 -0
- package/plugins/extract-rust/README.md +204 -0
- package/plugins/extract-rust/options.schema.json +65 -0
- package/plugins/extract-rust/rustfmt.toml +1 -0
- package/plugins/extract-sql/ddl.go +893 -0
- package/plugins/extract-sql/ddl_test.go +401 -0
- package/plugins/extract-sql/describe.go +19 -0
- package/plugins/extract-sql/describe_test.go +11 -0
- package/plugins/extract-sql/layout.go +221 -0
- package/plugins/extract-sql/layout_test.go +55 -0
- package/plugins/extract-sql/lineage.go +107 -0
- package/plugins/extract-sql/main.go +141 -0
- package/plugins/extract-sql/maps.go +564 -0
- package/plugins/extract-sql/maps_java.go +117 -0
- package/plugins/extract-sql/maps_rust.go +333 -0
- package/plugins/extract-sql/maps_rust_test.go +70 -0
- package/plugins/extract-sql/maps_test.go +204 -0
- package/plugins/extract-sql/maps_ts.go +398 -0
- package/plugins/extract-sql/maps_ts_test.go +136 -0
- package/plugins/extract-sql/options.schema.json +53 -0
- package/plugins/extract-sql/projection.go +70 -0
- package/plugins/extract-sql/projection_test.go +50 -0
- package/plugins/extract-sql/store.go +420 -0
- package/plugins/extract-sql/store_test.go +233 -0
- package/plugins/extract-sql/view.go +295 -0
- package/plugins/extract-ts/README.md +306 -0
- package/plugins/extract-ts/ast.test.ts +178 -0
- package/plugins/extract-ts/ast.ts +648 -0
- package/plugins/extract-ts/clients.ts +185 -0
- package/plugins/extract-ts/domain.ts +164 -0
- package/plugins/extract-ts/extract.test.ts +208 -0
- package/plugins/extract-ts/extract.ts +200 -0
- package/plugins/extract-ts/flows.ts +783 -0
- package/plugins/extract-ts/graphql.test.ts +73 -0
- package/plugins/extract-ts/graphql.ts +181 -0
- package/plugins/extract-ts/ids.test.ts +21 -0
- package/plugins/extract-ts/ids.ts +52 -0
- package/plugins/extract-ts/lifecycle.ts +138 -0
- package/plugins/extract-ts/main.ts +48 -0
- package/plugins/extract-ts/openapi.test.ts +64 -0
- package/plugins/extract-ts/openapi.ts +99 -0
- package/plugins/extract-ts/operations.ts +93 -0
- package/plugins/extract-ts/options.schema.json +96 -0
- package/plugins/extract-ts/source.test.ts +174 -0
- package/plugins/extract-ts/source.ts +465 -0
- package/plugins/extract-ts/transport.ts +147 -0
- package/plugins/extract-ts/tsconfig.json +13 -0
- package/plugins/extract-ts/wiring.ts +190 -0
- package/plugins/extract-watermill/describe.go +19 -0
- package/plugins/extract-watermill/describe_test.go +11 -0
- package/plugins/extract-watermill/extract.go +1228 -0
- package/plugins/extract-watermill/extract_test.go +234 -0
- package/plugins/extract-watermill/main.go +41 -0
- package/plugins/extract-watermill/options.schema.json +21 -0
- package/plugins/extract-wsdl/describe.go +19 -0
- package/plugins/extract-wsdl/describe_test.go +11 -0
- package/plugins/extract-wsdl/extract.go +148 -0
- package/plugins/extract-wsdl/extract_test.go +45 -0
- package/plugins/extract-wsdl/main.go +53 -0
- package/plugins/extract-wsdl/options.schema.json +59 -0
- package/plugins/fetch-bsr/auth.go +114 -0
- package/plugins/fetch-bsr/bsr.go +240 -0
- package/plugins/fetch-bsr/cache.go +66 -0
- package/plugins/fetch-bsr/describe.go +19 -0
- package/plugins/fetch-bsr/fetch.go +192 -0
- package/plugins/fetch-bsr/fetch_test.go +465 -0
- package/plugins/fetch-bsr/lock.go +71 -0
- package/plugins/fetch-bsr/main.go +117 -0
- package/plugins/fetch-bsr/options.schema.json +42 -0
- package/plugins/fetch-csr/auth.go +95 -0
- package/plugins/fetch-csr/cache.go +69 -0
- package/plugins/fetch-csr/describe.go +19 -0
- package/plugins/fetch-csr/describe_test.go +11 -0
- package/plugins/fetch-csr/fetch.go +254 -0
- package/plugins/fetch-csr/fetch_test.go +494 -0
- package/plugins/fetch-csr/lock.go +95 -0
- package/plugins/fetch-csr/main.go +122 -0
- package/plugins/fetch-csr/options.schema.json +42 -0
- package/plugins/fetch-csr/registry.go +209 -0
- package/plugins/fetch-git/cache.go +65 -0
- package/plugins/fetch-git/describe.go +19 -0
- package/plugins/fetch-git/describe_test.go +11 -0
- package/plugins/fetch-git/fetch.go +181 -0
- package/plugins/fetch-git/fetch_test.go +332 -0
- package/plugins/fetch-git/git.go +169 -0
- package/plugins/fetch-git/lock.go +72 -0
- package/plugins/fetch-git/main.go +127 -0
- package/plugins/fetch-git/offline.go +39 -0
- package/plugins/fetch-git/options.schema.json +42 -0
- package/plugins/fetch-git/pin.go +90 -0
- package/plugins/fetch-git/pin_test.go +74 -0
- package/plugins/gen-backstage/describe.go +17 -0
- package/plugins/gen-backstage/main.go +22 -0
- package/plugins/gen-backstage/options.schema.json +30 -0
- package/plugins/gen-backstage/plugin.go +473 -0
- package/plugins/gen-backstage/plugin_test.go +145 -0
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +162 -0
- package/plugins/gen-markdown/aggregate.go +362 -0
- package/plugins/gen-markdown/canonical.go +93 -0
- package/plugins/gen-markdown/context.go +90 -0
- package/plugins/gen-markdown/coverage_test.go +89 -0
- package/plugins/gen-markdown/describe.go +19 -0
- package/plugins/gen-markdown/describe_test.go +11 -0
- package/plugins/gen-markdown/external.go +71 -0
- package/plugins/gen-markdown/flow.go +277 -0
- package/plugins/gen-markdown/glossary.go +76 -0
- package/plugins/gen-markdown/glossary_test.go +148 -0
- package/plugins/gen-markdown/llms.go +302 -0
- package/plugins/gen-markdown/main.go +22 -0
- package/plugins/gen-markdown/markdown.go +254 -0
- package/plugins/gen-markdown/markdown_test.go +100 -0
- package/plugins/gen-markdown/module.go +107 -0
- package/plugins/gen-markdown/options.schema.json +16 -0
- package/plugins/gen-markdown/plugin.go +39 -0
- package/plugins/gen-markdown/quality_test.go +176 -0
- package/plugins/gen-markdown/redis_test.go +38 -0
- package/plugins/gen-markdown/render.go +344 -0
- package/plugins/gen-markdown/render_test.go +186 -0
- package/plugins/gen-markdown/service.go +456 -0
- package/plugins/gen-markdown/source.go +177 -0
- package/plugins/gen-markdown/store.go +201 -0
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +17 -0
- package/plugins/gen-mermaid/main.go +22 -0
- package/plugins/gen-mermaid/options.schema.json +10 -0
- package/plugins/gen-mermaid/plugin.go +104 -0
- package/plugins/gen-mermaid/plugin_test.go +33 -0
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/pyplugin/catalog.py +209 -0
- package/plugins/pyplugin/celery_conf.py +436 -0
- package/plugins/pyplugin/celery_tasks.py +76 -0
- package/plugins/pyplugin/kafka.py +570 -0
- package/plugins/pyplugin/names.py +38 -0
- package/plugins/pyplugin/protocol.py +79 -0
- package/plugins/pyplugin/source.py +285 -0
- package/plugins/verify-codeowners/describe.go +19 -0
- package/plugins/verify-codeowners/describe_test.go +11 -0
- package/plugins/verify-codeowners/main.go +75 -0
- package/plugins/verify-codeowners/match.go +85 -0
- package/plugins/verify-codeowners/match_test.go +47 -0
- package/plugins/verify-codeowners/options.schema.json +15 -0
- package/plugins/verify-codeowners/owners.go +164 -0
- package/plugins/verify-codeowners/owners_test.go +225 -0
- package/plugins/verify-codeowners/parse.go +90 -0
- package/plugins/verify-codeowners/parse_test.go +62 -0
- package/plugins/verify-otel/describe.go +19 -0
- package/plugins/verify-otel/describe_test.go +11 -0
- package/plugins/verify-otel/main.go +53 -0
- package/plugins/verify-otel/match.go +336 -0
- package/plugins/verify-otel/options.schema.json +30 -0
- package/plugins/verify-otel/otlp.go +200 -0
- package/plugins/verify-otel/verify.go +734 -0
- package/plugins/verify-otel/verify_test.go +460 -0
- package/portolan.json +1073 -0
- package/public/favicon.svg +12 -0
- package/schema/portolan.schema.json +2558 -0
- package/scripts/README.md +53 -0
- package/scripts/build-report.mjs +60 -0
- package/scripts/build-report.test.mjs +44 -0
- package/scripts/builtin-plugins.mjs +49 -0
- package/scripts/catalog-sources.mjs +67 -0
- package/scripts/diff.mjs +315 -0
- package/scripts/diff.test.mjs +151 -0
- package/scripts/forge-comment.mjs +144 -0
- package/scripts/forge-comment.test.mjs +151 -0
- package/scripts/forge-release.mjs +138 -0
- package/scripts/forge-release.test.mjs +106 -0
- package/scripts/forge.mjs +61 -0
- package/scripts/gen-likec4.mjs +854 -0
- package/scripts/gen-likec4.test.mjs +258 -0
- package/scripts/gen.mjs +603 -0
- package/scripts/generated-docs.test.mjs +66 -0
- package/scripts/local-api.mjs +1192 -0
- package/scripts/local-api.test.mjs +387 -0
- package/scripts/manifest.mjs +214 -0
- package/scripts/manifest.test.mjs +148 -0
- package/scripts/output-path.mjs +79 -0
- package/scripts/output-path.test.mjs +45 -0
- package/scripts/package-smoke.mjs +37 -0
- package/scripts/plugin-host.mjs +419 -0
- package/scripts/plugin-host.test.mjs +109 -0
- package/scripts/plugin-wasm-worker.mjs +34 -0
- package/scripts/run-builtin.mjs +81 -0
- package/scripts/schema.mjs +357 -0
- package/scripts/site-docs.mjs +295 -0
- package/scripts/site-docs.test.mjs +181 -0
- package/scripts/vendor-lock.mjs +58 -0
- package/scripts/vendor-lock.test.mjs +69 -0
- package/src/README.md +38 -0
- package/src/app/App.tsx +448 -0
- package/src/app/BranchPicker.test.ts +46 -0
- package/src/app/BranchPicker.tsx +153 -0
- package/src/app/Breadcrumbs.test.ts +38 -0
- package/src/app/Breadcrumbs.tsx +223 -0
- package/src/app/BuildStamp.tsx +146 -0
- package/src/app/CommandPalette.tsx +292 -0
- package/src/app/HashScroll.tsx +45 -0
- package/src/app/Sidebar.tsx +1742 -0
- package/src/app/TopBar.tsx +393 -0
- package/src/app/density.tsx +64 -0
- package/src/app/forge-access.test.ts +27 -0
- package/src/app/forge-access.tsx +80 -0
- package/src/app/panels.tsx +141 -0
- package/src/app/pins.tsx +242 -0
- package/src/app/query-client.ts +24 -0
- package/src/app/responsive.ts +55 -0
- package/src/app/shortcuts.test.ts +39 -0
- package/src/app/shortcuts.tsx +282 -0
- package/src/app/theme.tsx +51 -0
- package/src/app/title.test.ts +14 -0
- package/src/app/title.ts +30 -0
- package/src/app/toast.tsx +59 -0
- package/src/app/tree-keys.test.ts +80 -0
- package/src/app/tree-keys.ts +87 -0
- package/src/app/ui-store.ts +52 -0
- package/src/catalog-error.test.ts +105 -0
- package/src/catalog-profile.test.ts +59 -0
- package/src/catalog-profile.ts +101 -0
- package/src/catalog-stores.test.ts +431 -0
- package/src/catalog.test.ts +1215 -0
- package/src/catalog.ts +3125 -0
- package/src/chat/Cards.tsx +408 -0
- package/src/chat/ChatPanel.tsx +79 -0
- package/src/chat/ChatSettings.tsx +219 -0
- package/src/chat/Composer.tsx +99 -0
- package/src/chat/Conversation.tsx +281 -0
- package/src/chat/Header.tsx +46 -0
- package/src/chat/Message.tsx +49 -0
- package/src/chat/Notice.tsx +195 -0
- package/src/chat/Starter.tsx +67 -0
- package/src/chat/Steps.tsx +45 -0
- package/src/chat/Waiting.tsx +87 -0
- package/src/chat/flags.test.ts +59 -0
- package/src/chat/flags.ts +155 -0
- package/src/chat/linkify.test.ts +30 -0
- package/src/chat/linkify.ts +45 -0
- package/src/chat/prefs.ts +56 -0
- package/src/chat/prompt.test.ts +43 -0
- package/src/chat/prompt.ts +157 -0
- package/src/chat/store.ts +35 -0
- package/src/chat/tools.ts +58 -0
- package/src/chat/transport.ts +70 -0
- package/src/components/AdrRow.tsx +29 -0
- package/src/components/AnchorLink.tsx +67 -0
- package/src/components/ApiReference.tsx +134 -0
- package/src/components/AsyncApiReference.tsx +120 -0
- package/src/components/CatalogStamp.tsx +155 -0
- package/src/components/ChannelRows.tsx +132 -0
- package/src/components/CommandRows.tsx +139 -0
- package/src/components/DiagramSkeleton.tsx +71 -0
- package/src/components/EditorLink.tsx +85 -0
- package/src/components/FieldTree.tsx +305 -0
- package/src/components/FlowTrigger.tsx +34 -0
- package/src/components/Ident.tsx +89 -0
- package/src/components/Integrations.tsx +243 -0
- package/src/components/LifecycleDiagram.tsx +110 -0
- package/src/components/MachineDocs.tsx +42 -0
- package/src/components/Markdown.tsx +173 -0
- package/src/components/MarkdownTable.tsx +206 -0
- package/src/components/Mermaid.tsx +211 -0
- package/src/components/MethodRows.tsx +309 -0
- package/src/components/Overlay.tsx +125 -0
- package/src/components/PageHeader.tsx +189 -0
- package/src/components/RowActions.tsx +97 -0
- package/src/components/SchemaDocument.tsx +89 -0
- package/src/components/Select.tsx +126 -0
- package/src/components/ShapeRows.tsx +118 -0
- package/src/components/SourceDoc.tsx +82 -0
- package/src/components/SourcePreview.tsx +441 -0
- package/src/components/TabRow.tsx +182 -0
- package/src/components/TechIcon.tsx +34 -0
- package/src/components/Toc.tsx +132 -0
- package/src/components/WhatLinksHere.tsx +273 -0
- package/src/components/WsdlReference.tsx +84 -0
- package/src/components/ddd-icons.tsx +107 -0
- package/src/components/kind.tsx +157 -0
- package/src/components/logo.tsx +61 -0
- package/src/components/primitives.tsx +264 -0
- package/src/data.test.ts +18 -0
- package/src/data.ts +202 -0
- package/src/enrich.test.ts +1150 -0
- package/src/enrich.ts +672 -0
- package/src/er/ErCanvas.tsx +533 -0
- package/src/er/RedisSchema.tsx +74 -0
- package/src/er/RelationCard.tsx +267 -0
- package/src/er/StoreHeader.tsx +155 -0
- package/src/er/TableNode.tsx +84 -0
- package/src/er/ViewNode.tsx +83 -0
- package/src/er/layout.test.ts +115 -0
- package/src/er/layout.ts +77 -0
- package/src/er/lineage.test.ts +140 -0
- package/src/er/lineage.ts +107 -0
- package/src/er/markers.tsx +79 -0
- package/src/er/spec.test.ts +344 -0
- package/src/er/spec.ts +426 -0
- package/src/flow/ChainList.tsx +162 -0
- package/src/flow/FlowTable.tsx +123 -0
- package/src/flow/FlowToolbar.tsx +372 -0
- package/src/flow/StepDetail.tsx +380 -0
- package/src/flow/StepRail.tsx +428 -0
- package/src/flow/answers.test.ts +50 -0
- package/src/flow/answers.ts +58 -0
- package/src/flow/chain.test.ts +383 -0
- package/src/flow/chain.ts +353 -0
- package/src/flow/chapters.test.ts +185 -0
- package/src/flow/chapters.ts +225 -0
- package/src/flow/continues.test.ts +102 -0
- package/src/flow/continues.ts +65 -0
- package/src/flow/cross-context.test.ts +97 -0
- package/src/flow/cross-context.ts +35 -0
- package/src/flow/mermaid.test.ts +22 -0
- package/src/flow/mermaid.ts +141 -0
- package/src/flow/outline.test.ts +212 -0
- package/src/flow/outline.ts +170 -0
- package/src/flow/paths.test.ts +129 -0
- package/src/flow/paths.ts +147 -0
- package/src/flow/prefs.test.ts +80 -0
- package/src/flow/prefs.ts +104 -0
- package/src/graph/DependencyGraph.tsx +283 -0
- package/src/graph/DependencyNodes.tsx +162 -0
- package/src/graph/EdgeLabel.tsx +61 -0
- package/src/graph/FocusedEventGraph.tsx +211 -0
- package/src/graph/GraphToolbar.tsx +187 -0
- package/src/graph/RoutedEdge.tsx +80 -0
- package/src/graph/dependency-layout.test.ts +360 -0
- package/src/graph/dependency-layout.ts +444 -0
- package/src/graph/elk.test.ts +70 -0
- package/src/graph/elk.ts +287 -0
- package/src/graph/nodes.tsx +97 -0
- package/src/graph/theme.ts +81 -0
- package/src/graph/useElkFlow.ts +125 -0
- package/src/index.css +1547 -0
- package/src/language/InLanguage.tsx +40 -0
- package/src/language/TermCard.tsx +132 -0
- package/src/language/cards.test.ts +114 -0
- package/src/language/cards.ts +111 -0
- package/src/lib/admonition.test.ts +49 -0
- package/src/lib/admonition.ts +48 -0
- package/src/lib/adr.test.ts +131 -0
- package/src/lib/adr.ts +68 -0
- package/src/lib/api.test.ts +122 -0
- package/src/lib/api.ts +135 -0
- package/src/lib/backlinks.test.ts +330 -0
- package/src/lib/backlinks.ts +567 -0
- package/src/lib/branch-compare.ts +16 -0
- package/src/lib/build-info.test.ts +76 -0
- package/src/lib/build-info.ts +80 -0
- package/src/lib/catalog-diff.test.ts +275 -0
- package/src/lib/catalog-diff.ts +655 -0
- package/src/lib/clipboard.ts +43 -0
- package/src/lib/comparison-memory.test.ts +40 -0
- package/src/lib/comparison-memory.ts +39 -0
- package/src/lib/context-color.ts +27 -0
- package/src/lib/context-map.test.ts +148 -0
- package/src/lib/context-map.ts +399 -0
- package/src/lib/data-model.test.ts +245 -0
- package/src/lib/data-model.ts +399 -0
- package/src/lib/data-problems.test.ts +178 -0
- package/src/lib/data-problems.ts +314 -0
- package/src/lib/derive.test.ts +251 -0
- package/src/lib/derive.ts +515 -0
- package/src/lib/editor-link.test.ts +125 -0
- package/src/lib/editor-link.ts +96 -0
- package/src/lib/editor-prefs.ts +27 -0
- package/src/lib/event-graph.test.ts +237 -0
- package/src/lib/event-graph.ts +327 -0
- package/src/lib/export-canvas.ts +37 -0
- package/src/lib/export-file.test.ts +30 -0
- package/src/lib/export-file.ts +20 -0
- package/src/lib/flow-tree.test.ts +177 -0
- package/src/lib/flow-tree.ts +160 -0
- package/src/lib/forge-refs.test.ts +50 -0
- package/src/lib/forge-refs.ts +60 -0
- package/src/lib/format.test.ts +35 -0
- package/src/lib/format.ts +64 -0
- package/src/lib/github-catalog.test.ts +301 -0
- package/src/lib/github-catalog.ts +610 -0
- package/src/lib/integrations.test.ts +119 -0
- package/src/lib/integrations.ts +133 -0
- package/src/lib/kinds.test.ts +91 -0
- package/src/lib/kinds.ts +204 -0
- package/src/lib/lifecycle.test.ts +93 -0
- package/src/lib/lifecycle.ts +165 -0
- package/src/lib/local-api.ts +194 -0
- package/src/lib/motion-features.ts +11 -0
- package/src/lib/motion.test.ts +120 -0
- package/src/lib/motion.tsx +269 -0
- package/src/lib/palette-recent.test.ts +80 -0
- package/src/lib/palette-recent.ts +90 -0
- package/src/lib/palette.test.ts +365 -0
- package/src/lib/palette.ts +593 -0
- package/src/lib/pins.test.ts +139 -0
- package/src/lib/pins.ts +130 -0
- package/src/lib/problems.test.ts +112 -0
- package/src/lib/project-preview.ts +5 -0
- package/src/lib/proto-problems.test.ts +121 -0
- package/src/lib/proto-problems.ts +65 -0
- package/src/lib/queries.test.ts +113 -0
- package/src/lib/queries.ts +131 -0
- package/src/lib/registry.test.ts +219 -0
- package/src/lib/registry.ts +208 -0
- package/src/lib/scenarios.ts +547 -0
- package/src/lib/setup-info.test.ts +199 -0
- package/src/lib/setup-info.ts +415 -0
- package/src/lib/shape.test.ts +289 -0
- package/src/lib/shape.ts +454 -0
- package/src/lib/sidebar-prefs.test.ts +32 -0
- package/src/lib/sidebar-prefs.ts +75 -0
- package/src/lib/source-code.test.ts +159 -0
- package/src/lib/source-code.ts +286 -0
- package/src/lib/source-doc.test.ts +131 -0
- package/src/lib/source-doc.ts +103 -0
- package/src/lib/source-highlight.test.ts +29 -0
- package/src/lib/source-highlight.ts +24 -0
- package/src/lib/source-link.test.ts +153 -0
- package/src/lib/source-link.ts +201 -0
- package/src/lib/spec-files.test.ts +24 -0
- package/src/lib/spec-files.ts +23 -0
- package/src/lib/table-directive.test.ts +139 -0
- package/src/lib/table-directive.ts +106 -0
- package/src/lib/tech.test.ts +120 -0
- package/src/lib/tech.ts +421 -0
- package/src/lib/terms.test.ts +67 -0
- package/src/lib/terms.ts +179 -0
- package/src/lib/wire-problems.test.ts +263 -0
- package/src/lib/wire-problems.ts +281 -0
- package/src/likec4/C4View.tsx +55 -0
- package/src/likec4/CanvasBridge.tsx +187 -0
- package/src/likec4/FlowView.tsx +235 -0
- package/src/likec4/InteractiveView.tsx +144 -0
- package/src/likec4/canvas-viewport.test.ts +149 -0
- package/src/likec4/canvas-viewport.ts +135 -0
- package/src/likec4/flow-edges.test.ts +51 -0
- package/src/likec4/flow-edges.ts +89 -0
- package/src/likec4/frame-theme.test.ts +76 -0
- package/src/likec4/frame-theme.ts +101 -0
- package/src/likec4/highlight-css.test.ts +49 -0
- package/src/likec4/highlight-css.ts +52 -0
- package/src/likec4/ids.test.ts +151 -0
- package/src/likec4/ids.ts +115 -0
- package/src/likec4/levels.tsx +70 -0
- package/src/likec4/mapping.test.ts +66 -0
- package/src/likec4/mapping.ts +54 -0
- package/src/likec4/reserved.json +141 -0
- package/src/likec4/view-index.ts +78 -0
- package/src/main.tsx +13 -0
- package/src/map/ContextMapGraph.tsx +274 -0
- package/src/map/ContextNode.tsx +70 -0
- package/src/map/FloatingEdge.tsx +86 -0
- package/src/merge.test.ts +1038 -0
- package/src/merge.ts +1004 -0
- package/src/pages/AdrDetail.tsx +242 -0
- package/src/pages/AdrIndex.tsx +114 -0
- package/src/pages/AggregatePage.tsx +727 -0
- package/src/pages/BlockPage.tsx +478 -0
- package/src/pages/CatalogFailure.tsx +111 -0
- package/src/pages/Changes.tsx +313 -0
- package/src/pages/ContextMap.tsx +534 -0
- package/src/pages/ContextPage.tsx +346 -0
- package/src/pages/EnumPage.tsx +282 -0
- package/src/pages/EventPage.tsx +686 -0
- package/src/pages/ExternalPage.tsx +204 -0
- package/src/pages/FlowDetail.tsx +663 -0
- package/src/pages/FlowIndex.tsx +303 -0
- package/src/pages/GraphPage.tsx +217 -0
- package/src/pages/Language.tsx +257 -0
- package/src/pages/ModulePage.tsx +366 -0
- package/src/pages/NotFound.tsx +54 -0
- package/src/pages/Overview.tsx +344 -0
- package/src/pages/Problems.tsx +360 -0
- package/src/pages/RegistryIndex.tsx +118 -0
- package/src/pages/ServicePage.tsx +746 -0
- package/src/pages/Settings.tsx +975 -0
- package/src/pages/StorePage.tsx +114 -0
- package/src/peek/PeekCard.test.ts +35 -0
- package/src/peek/PeekCard.tsx +118 -0
- package/src/peek/PeekLayer.tsx +147 -0
- package/src/peek/model.test.ts +156 -0
- package/src/peek/model.ts +253 -0
- package/src/routes.test.ts +222 -0
- package/src/routes.ts +449 -0
- package/src/selection/DetailPanel.tsx +1407 -0
- package/src/selection/hash.test.ts +73 -0
- package/src/selection/hash.ts +58 -0
- package/src/selection/model.test.ts +199 -0
- package/src/selection/model.ts +473 -0
- package/src/selection/pages.test.ts +115 -0
- package/src/selection/pages.ts +268 -0
- package/src/selection/store.test.ts +62 -0
- package/src/selection/store.ts +46 -0
- package/src/selection/sync.tsx +99 -0
- package/src/table/ColumnsMenu.tsx +72 -0
- package/src/table/DataTable.tsx +780 -0
- package/src/table/Facets.tsx +54 -0
- package/src/table/GroupMenu.tsx +69 -0
- package/src/table/Toolbar.tsx +190 -0
- package/src/table/cells.tsx +117 -0
- package/src/table/compare.test.ts +117 -0
- package/src/table/compare.ts +196 -0
- package/src/table/export.test.ts +88 -0
- package/src/table/export.ts +66 -0
- package/src/table/facet-groups.test.ts +74 -0
- package/src/table/facet-groups.ts +56 -0
- package/src/table/grouping.test.ts +84 -0
- package/src/table/grouping.ts +82 -0
- package/src/table/infer.test.ts +57 -0
- package/src/table/infer.ts +50 -0
- package/src/table/persist.test.ts +76 -0
- package/src/table/persist.ts +112 -0
- package/src/table/sort-url.test.ts +75 -0
- package/src/table/sort-url.ts +52 -0
- package/src/table/types.ts +80 -0
- package/src/table/useTableState.ts +220 -0
- package/src/test-catalog.ts +7 -0
- package/src/testing/estate/data__catalog.json +2847 -0
- package/src/testing/estate/data__flows.json +1724 -0
- package/src/testing/estate/examples__auth__portolan__api.json +212 -0
- package/src/testing/estate/examples__auth__portolan__domain.json +1204 -0
- package/src/testing/estate/examples__auth__portolan__glossary.json +226 -0
- package/src/testing/estate/examples__auth__portolan__observed.json +757 -0
- package/src/testing/estate/examples__auth__portolan__stores.json +195 -0
- package/src/testing/estate/examples__shop__cart__portolan__api.json +234 -0
- package/src/testing/estate/examples__shop__cart__portolan__commands.json +86 -0
- package/src/testing/estate/examples__shop__cart__portolan__domain.json +966 -0
- package/src/testing/estate/examples__shop__cart__portolan__observed.json +498 -0
- package/src/testing/estate/examples__shop__cart__portolan__stores.json +208 -0
- package/src/testing/estate/examples__shop__oms__portolan__glossary.json +98 -0
- package/src/testing/estate/examples__shop__oms__portolan__proto.json +186 -0
- package/src/testing/estate.ts +49 -0
- package/src/trail/Trail.tsx +81 -0
- package/src/trail/model.test.ts +157 -0
- package/src/trail/model.ts +189 -0
- package/src/trail/record.tsx +32 -0
- package/src/trail/store.test.ts +66 -0
- package/src/trail/store.ts +102 -0
- package/tsconfig.json +35 -0
- package/vite.config.ts +171 -0
|
@@ -0,0 +1,1441 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/ast"
|
|
5
|
+
"go/types"
|
|
6
|
+
"path"
|
|
7
|
+
"sort"
|
|
8
|
+
"strconv"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/shortlink-org/portolan/catalog"
|
|
12
|
+
"github.com/shortlink-org/portolan/plugin"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// Flows are read out of the same layout as everything else here, and the
|
|
16
|
+
// layout is again the claim.
|
|
17
|
+
//
|
|
18
|
+
// Two things start one. An endpoint is somebody calling in, and the transport
|
|
19
|
+
// layer already says which use cases it runs and in what order. A policy is an
|
|
20
|
+
// event arriving, and the type it asserts on says which. Everything after that
|
|
21
|
+
// is the use case's own body: a field of UseCase is a port, a call on that port
|
|
22
|
+
// is a hop, and a domain call whose signature returns an event is what puts the
|
|
23
|
+
// event on the bus.
|
|
24
|
+
//
|
|
25
|
+
// What this cannot do it does not pretend to. An `if` or a `switch` becomes an
|
|
26
|
+
// alt only when something happens inside it - a hop, a publish - and its
|
|
27
|
+
// branch is terminal when the block ends in a return; an `if err != nil { return err }` with
|
|
28
|
+
// nothing in it is not an alternative path, it is the end of this one, and
|
|
29
|
+
// forty empty frames would say less than none. A step inside a loop carries a
|
|
30
|
+
// note saying so rather than a frame the reader would have to trust. Nothing
|
|
31
|
+
// is observed running, so every step is declared: this reads code, and code is
|
|
32
|
+
// a claim about behaviour, not a record of it. The one exception is a call
|
|
33
|
+
// whose far end the manifest does not name, which is unresolved.
|
|
34
|
+
|
|
35
|
+
const (
|
|
36
|
+
laneClient = "client"
|
|
37
|
+
laneBus = "bus"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// maxInline is how far a flow follows a use case into another one. Two is the
|
|
41
|
+
// whole of this estate - an endpoint into a use case into the use case behind
|
|
42
|
+
// its port - and a limit that is never reached is a limit that never has to be
|
|
43
|
+
// explained.
|
|
44
|
+
const maxInline = 2
|
|
45
|
+
|
|
46
|
+
type flowOptions struct {
|
|
47
|
+
context string
|
|
48
|
+
svcID string
|
|
49
|
+
service string
|
|
50
|
+
// store is the slug the manifest gives the service's database. Without it
|
|
51
|
+
// there is no lane to put persistence on, and the calls stay on the
|
|
52
|
+
// service's own.
|
|
53
|
+
store string
|
|
54
|
+
// peers maps a proto package to the service that answers to it.
|
|
55
|
+
peers map[string]string
|
|
56
|
+
// externals maps an api id to the system outside the estate that answers
|
|
57
|
+
// to it, when the manifest names one rather than leaving it to the
|
|
58
|
+
// document beside the client.
|
|
59
|
+
externals map[string]string
|
|
60
|
+
// events maps the import path a foreign event is vendored under to the
|
|
61
|
+
// aggregate that raised it.
|
|
62
|
+
events map[string]string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type flowReader struct {
|
|
66
|
+
root string
|
|
67
|
+
opts flowOptions
|
|
68
|
+
layout sourceLayout
|
|
69
|
+
b *plugin.Builder
|
|
70
|
+
bindings map[string]string
|
|
71
|
+
useCases map[string]*pkg
|
|
72
|
+
domains map[string]*pkg
|
|
73
|
+
// referenced records the events some step named, so that an event nothing
|
|
74
|
+
// here could follow can be reported rather than silently left out.
|
|
75
|
+
referenced map[string]bool
|
|
76
|
+
warnedStore bool
|
|
77
|
+
// module is the module path of go.mod, which is how an import path is
|
|
78
|
+
// read back to a directory in the tree.
|
|
79
|
+
module string
|
|
80
|
+
// adapters are the ports assembly fills with something other than a use
|
|
81
|
+
// case; clients are the generated clients already read, by import path.
|
|
82
|
+
adapters map[string]adapterDecl
|
|
83
|
+
wireBoundPorts map[string]bool
|
|
84
|
+
clients map[string]map[string]client
|
|
85
|
+
// calls are the rpcs some step made, by id, for the service's consumes.
|
|
86
|
+
calls map[string]catalog.RpcCall
|
|
87
|
+
warnedPeer map[string]bool
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// extractFlows reads every sequence the service runs, in a fixed order:
|
|
91
|
+
// endpoints by operation id, then policies by type name.
|
|
92
|
+
func extractFlows(root string, opts flowOptions, layout sourceLayout, endpoints []endpointDecl, events []string, b *plugin.Builder) ([]catalog.Flow, []catalog.RpcCall) {
|
|
93
|
+
r := newFlowReader(root, opts, b, layout)
|
|
94
|
+
|
|
95
|
+
out := []catalog.Flow{}
|
|
96
|
+
for _, endpoint := range endpoints {
|
|
97
|
+
if flow, ok := r.endpointFlow(endpoint); ok {
|
|
98
|
+
out = append(out, flow)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
out = append(out, r.policyFlows()...)
|
|
102
|
+
|
|
103
|
+
for _, event := range events {
|
|
104
|
+
if !r.referenced[event] {
|
|
105
|
+
b.Warn(event, "no flow reaches this event: nothing this extractor could follow publishes it")
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return out, r.consumes()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func newFlowReader(root string, opts flowOptions, b *plugin.Builder, layouts ...sourceLayout) *flowReader {
|
|
113
|
+
layout := discoverLayout(root)
|
|
114
|
+
if len(layouts) > 0 {
|
|
115
|
+
layout = layouts[0]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return &flowReader{
|
|
119
|
+
root: root,
|
|
120
|
+
opts: opts,
|
|
121
|
+
layout: layout,
|
|
122
|
+
b: b,
|
|
123
|
+
bindings: portBindings(root, layout),
|
|
124
|
+
adapters: adapterBindings(root, layout),
|
|
125
|
+
wireBoundPorts: wireBoundPorts(root, layout),
|
|
126
|
+
module: modulePath(root),
|
|
127
|
+
useCases: map[string]*pkg{},
|
|
128
|
+
domains: map[string]*pkg{},
|
|
129
|
+
clients: map[string]map[string]client{},
|
|
130
|
+
calls: map[string]catalog.RpcCall{},
|
|
131
|
+
referenced: map[string]bool{},
|
|
132
|
+
warnedPeer: map[string]bool{},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// consumes is every rpc the flows made, in id order, so the fragment is the
|
|
137
|
+
// same however the use cases happened to be read.
|
|
138
|
+
func (r *flowReader) consumes() []catalog.RpcCall {
|
|
139
|
+
out := make([]catalog.RpcCall, 0, len(r.calls))
|
|
140
|
+
for _, call := range r.calls {
|
|
141
|
+
out = append(out, call)
|
|
142
|
+
}
|
|
143
|
+
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
|
|
144
|
+
|
|
145
|
+
return out
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// endpointFlow opens with the call from outside and continues into whatever the
|
|
149
|
+
// handler runs.
|
|
150
|
+
//
|
|
151
|
+
// The flow is named after the endpoint rather than after a use case because the
|
|
152
|
+
// endpoint is the thing that happened; where two use cases run behind one, the
|
|
153
|
+
// prose comes from the last of them, which is the one the endpoint is FOR - the
|
|
154
|
+
// others authorized it.
|
|
155
|
+
func (r *flowReader) endpointFlow(endpoint endpointDecl) (catalog.Flow, bool) {
|
|
156
|
+
d := newDraft()
|
|
157
|
+
d.lane(catalog.Participant{ID: laneClient, Kind: catalog.ParticipantActor})
|
|
158
|
+
d.lane(r.serviceLane())
|
|
159
|
+
|
|
160
|
+
d.add(catalog.Step{
|
|
161
|
+
From: laneClient,
|
|
162
|
+
To: r.opts.svcID,
|
|
163
|
+
Kind: catalog.StepRPC,
|
|
164
|
+
Label: endpoint.id,
|
|
165
|
+
Line: at(endpoint.source, endpoint.line),
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
for _, useCase := range endpoint.useCases {
|
|
169
|
+
r.walkUseCase(d, useCase, 0)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
last := endpoint.useCases[len(endpoint.useCases)-1]
|
|
173
|
+
name := slug(endpoint.id)
|
|
174
|
+
id := r.opts.service + "-" + name
|
|
175
|
+
|
|
176
|
+
return catalog.Flow{
|
|
177
|
+
ID: "flow." + id,
|
|
178
|
+
Slug: id,
|
|
179
|
+
Name: sentence(name),
|
|
180
|
+
Summary: r.useCaseDoc(last),
|
|
181
|
+
Source: endpoint.source,
|
|
182
|
+
Owner: r.opts.context,
|
|
183
|
+
Participants: d.lanes,
|
|
184
|
+
Steps: d.steps,
|
|
185
|
+
}, true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// policyFlows reads every discovered policy package: a rule of the form "when
|
|
189
|
+
// X has happened, do Y", which is a flow that opens on the bus.
|
|
190
|
+
//
|
|
191
|
+
// It opens with the event rather than with the policy, and that is what lets
|
|
192
|
+
// the two halves meet: a flow whose FIRST step names the same event another
|
|
193
|
+
// flow publishes is that flow's continuation, and nothing has to write the seam
|
|
194
|
+
// down for a reader to follow it.
|
|
195
|
+
func (r *flowReader) policyFlows() []catalog.Flow {
|
|
196
|
+
out := []catalog.Flow{}
|
|
197
|
+
|
|
198
|
+
for _, dir := range r.layout.policies {
|
|
199
|
+
out = append(out, r.policyFlowsIn(dir)...)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return out
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func (r *flowReader) policyFlowsIn(dir string) []catalog.Flow {
|
|
206
|
+
out := []catalog.Flow{}
|
|
207
|
+
|
|
208
|
+
pkg, err := parsePkg(r.root, dir)
|
|
209
|
+
if err != nil {
|
|
210
|
+
return out
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
imports := importsOf(pkg)
|
|
214
|
+
|
|
215
|
+
names := []string{}
|
|
216
|
+
byName := map[string]structDecl{}
|
|
217
|
+
for _, decl := range pkg.structs() {
|
|
218
|
+
if !exported(decl.name) {
|
|
219
|
+
continue
|
|
220
|
+
}
|
|
221
|
+
names = append(names, decl.name)
|
|
222
|
+
byName[decl.name] = decl
|
|
223
|
+
}
|
|
224
|
+
sort.Strings(names)
|
|
225
|
+
|
|
226
|
+
for _, name := range names {
|
|
227
|
+
decl := byName[name]
|
|
228
|
+
|
|
229
|
+
handle := pkg.methods(name)["Handle"]
|
|
230
|
+
if handle == nil {
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
event, ok := r.assertedEvent(handle, imports)
|
|
235
|
+
if !ok {
|
|
236
|
+
r.b.Warn(name, pkg.dir+": "+name+".Handle asserts on no event type; the policy is not paired with what triggers it")
|
|
237
|
+
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
d := newDraft()
|
|
242
|
+
d.lane(catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker})
|
|
243
|
+
d.lane(r.serviceLane())
|
|
244
|
+
|
|
245
|
+
source, line := pkg.position(handle.Pos())
|
|
246
|
+
if event.foreign != "" {
|
|
247
|
+
// Another repository's event. Its id is <service>.<aggregate>.<Name>
|
|
248
|
+
// and this tree knows neither half, so the step names what it
|
|
249
|
+
// asserts on and says it resolves to nothing here; the merge is
|
|
250
|
+
// where the other side may turn up.
|
|
251
|
+
r.b.Warn(name, pkg.dir+": "+name+".Handle asserts on "+event.foreign+"."+event.name+", an event this repository does not declare; the step is unresolved")
|
|
252
|
+
d.add(catalog.Step{
|
|
253
|
+
From: laneBus,
|
|
254
|
+
To: r.opts.svcID,
|
|
255
|
+
Kind: catalog.StepEvent,
|
|
256
|
+
Label: event.name,
|
|
257
|
+
Status: catalog.StatusUnresolved,
|
|
258
|
+
Note: "Reacts to `" + event.name + "` from `" + event.foreign + "`, which is not an event this repository declares.",
|
|
259
|
+
Line: at(source, line),
|
|
260
|
+
})
|
|
261
|
+
} else {
|
|
262
|
+
d.add(catalog.Step{
|
|
263
|
+
From: laneBus,
|
|
264
|
+
To: r.opts.svcID,
|
|
265
|
+
Kind: catalog.StepEvent,
|
|
266
|
+
Ref: event.id,
|
|
267
|
+
Label: event.name,
|
|
268
|
+
Line: at(source, line),
|
|
269
|
+
})
|
|
270
|
+
r.referenced[event.id] = true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
r.walkBody(d, &scope{
|
|
274
|
+
pkg: pkg,
|
|
275
|
+
fields: structFields(decl.fields),
|
|
276
|
+
imports: imports,
|
|
277
|
+
vars: map[string]domainRef{},
|
|
278
|
+
recv: receiverIdent(handle),
|
|
279
|
+
recvType: name,
|
|
280
|
+
}, handle, 0)
|
|
281
|
+
|
|
282
|
+
id := r.opts.service + "-" + slug(name)
|
|
283
|
+
out = append(out, catalog.Flow{
|
|
284
|
+
ID: "flow." + id,
|
|
285
|
+
Slug: id,
|
|
286
|
+
Name: sentence(slug(name)),
|
|
287
|
+
Summary: withoutLeading(name+" ", decl.doc),
|
|
288
|
+
Source: decl.source,
|
|
289
|
+
Owner: r.opts.context,
|
|
290
|
+
Participants: d.lanes,
|
|
291
|
+
Steps: d.steps,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return out
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// assertedEvent reads `changed, ok := e.(userevent.PasswordChanged)`: the type
|
|
299
|
+
// a policy asserts on is the fact it reacts to. A type from outside this
|
|
300
|
+
// module is somebody else's event; it is returned with `foreign` set to where
|
|
301
|
+
// it came from, because the policy exists whether or not its trigger does.
|
|
302
|
+
func (r *flowReader) assertedEvent(fn *ast.FuncDecl, imports map[string]string) (eventRef, bool) {
|
|
303
|
+
var out eventRef
|
|
304
|
+
found := false
|
|
305
|
+
|
|
306
|
+
ast.Inspect(fn.Body, func(node ast.Node) bool {
|
|
307
|
+
assert, ok := node.(*ast.TypeAssertExpr)
|
|
308
|
+
if !ok || assert.Type == nil || found {
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
selector, name, cut := strings.Cut(types.ExprString(assert.Type), ".")
|
|
313
|
+
if !cut {
|
|
314
|
+
return true
|
|
315
|
+
}
|
|
316
|
+
importPath := imports[selector]
|
|
317
|
+
if aggregate, isEvent := eventPackage(importPath); isEvent {
|
|
318
|
+
out = eventRef{id: eventID(aggregateID(r.opts.svcID, aggregate), name), name: name}
|
|
319
|
+
found = true
|
|
320
|
+
|
|
321
|
+
return false
|
|
322
|
+
}
|
|
323
|
+
// Somebody else's event, vendored into this tree as a shape of its own.
|
|
324
|
+
// The manifest is the only place that knows whose aggregate raised it.
|
|
325
|
+
if aggregate, placed := r.opts.events[importPath]; placed {
|
|
326
|
+
out = eventRef{id: eventID(aggregate, name), name: name}
|
|
327
|
+
found = true
|
|
328
|
+
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
// The same thing with nobody to place it: named, and left to the merge.
|
|
332
|
+
if importPath != "" && (strings.Contains(importPath, "/internal/infrastructure/") || strings.Contains(importPath, "/internal/integration/")) {
|
|
333
|
+
out = eventRef{name: name, foreign: importPath}
|
|
334
|
+
found = true
|
|
335
|
+
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
if importPath != "" && r.module != "" && !strings.HasPrefix(importPath, r.module+"/") && !isStandard(importPath) {
|
|
339
|
+
out = eventRef{name: name, foreign: importPath}
|
|
340
|
+
found = true
|
|
341
|
+
|
|
342
|
+
return false
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return true
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
return out, found
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// isStandard is the one thing an import path says about itself: a standard
|
|
352
|
+
// library path has no dot in its first segment.
|
|
353
|
+
func isStandard(importPath string) bool {
|
|
354
|
+
first, _, _ := strings.Cut(importPath, "/")
|
|
355
|
+
|
|
356
|
+
return !strings.Contains(first, ".")
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
type eventRef struct {
|
|
360
|
+
// foreign is the import path of an event another repository declares.
|
|
361
|
+
foreign string
|
|
362
|
+
id string
|
|
363
|
+
name string
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// reading one use case
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
// scope is one function body being read: what the receiver is called, what
|
|
371
|
+
// ports its type holds, and what each local name has turned out to hold.
|
|
372
|
+
type scope struct {
|
|
373
|
+
pkg *pkg
|
|
374
|
+
key string
|
|
375
|
+
fields map[string]string
|
|
376
|
+
imports map[string]string
|
|
377
|
+
vars map[string]domainRef
|
|
378
|
+
recv string
|
|
379
|
+
// recvType is what the receiver's methods hang off: UseCase for a use
|
|
380
|
+
// case, its own name for a policy.
|
|
381
|
+
recvType string
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// domainRef is a value whose type belongs to the service's own domain. Nothing
|
|
385
|
+
// else is tracked: a string is a string, and the only reason to follow a value
|
|
386
|
+
// at all is to notice when it is an event on its way to the bus.
|
|
387
|
+
type domainRef struct {
|
|
388
|
+
aggregate string
|
|
389
|
+
name string
|
|
390
|
+
event bool
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
func (r *flowReader) walkUseCase(d *flowDraft, key string, depth int) {
|
|
394
|
+
if depth > maxInline || d.seen[key] {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
d.seen[key] = true
|
|
398
|
+
|
|
399
|
+
pkg := r.useCasePkg(key)
|
|
400
|
+
if pkg == nil {
|
|
401
|
+
return
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
handle := pkg.methods("UseCase")["Handle"]
|
|
405
|
+
if handle == nil {
|
|
406
|
+
r.b.Warn(key, pkg.dir+": no Handle on UseCase; the use case contributes no steps")
|
|
407
|
+
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
r.walkBody(d, &scope{
|
|
412
|
+
pkg: pkg,
|
|
413
|
+
key: key,
|
|
414
|
+
fields: useCaseFields(pkg),
|
|
415
|
+
imports: importsOf(pkg),
|
|
416
|
+
vars: map[string]domainRef{},
|
|
417
|
+
recv: receiverIdent(handle),
|
|
418
|
+
recvType: "UseCase",
|
|
419
|
+
}, handle, depth)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
func (r *flowReader) walkBody(d *flowDraft, s *scope, fn *ast.FuncDecl, depth int) {
|
|
423
|
+
if fn == nil || fn.Body == nil {
|
|
424
|
+
return
|
|
425
|
+
}
|
|
426
|
+
r.walkStmts(d, s, fn.Body.List, depth)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
func (r *flowReader) walkStmts(d *flowDraft, s *scope, list []ast.Stmt, depth int) {
|
|
430
|
+
for _, stmt := range list {
|
|
431
|
+
r.walkStmt(d, s, stmt, depth)
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// walkStmt reads one statement in source order. An `if` may become a frame, a
|
|
436
|
+
// loop becomes a note on what it encloses, and everything else contributes
|
|
437
|
+
// its calls, in the order they are written, and no frame: a switch is a
|
|
438
|
+
// choice too, but its arms are values rather than conditions in words, and
|
|
439
|
+
// reading it as an alt is a later question.
|
|
440
|
+
func (r *flowReader) walkStmt(d *flowDraft, s *scope, stmt ast.Stmt, depth int) {
|
|
441
|
+
switch x := stmt.(type) {
|
|
442
|
+
case *ast.IfStmt:
|
|
443
|
+
r.walkIf(d, s, x, depth)
|
|
444
|
+
case *ast.ForStmt:
|
|
445
|
+
if x.Init != nil {
|
|
446
|
+
r.walkStmt(d, s, x.Init, depth)
|
|
447
|
+
}
|
|
448
|
+
if x.Cond != nil {
|
|
449
|
+
r.callsIn(d, s, x.Cond, depth)
|
|
450
|
+
}
|
|
451
|
+
d.enter(loopTitle(x))
|
|
452
|
+
r.walkStmts(d, s, x.Body.List, depth)
|
|
453
|
+
d.leave()
|
|
454
|
+
case *ast.RangeStmt:
|
|
455
|
+
r.callsIn(d, s, x.X, depth)
|
|
456
|
+
// `for _, s := range sessions`: the element is what the list holds.
|
|
457
|
+
// A list came back from a port as `[]*Session`, so its element is a
|
|
458
|
+
// Session, and what is called on it can be read the same way.
|
|
459
|
+
if list, ok := x.X.(*ast.Ident); ok {
|
|
460
|
+
if ref, tracked := s.vars[list.Name]; tracked {
|
|
461
|
+
if value, ok := x.Value.(*ast.Ident); ok && value.Name != "_" {
|
|
462
|
+
s.vars[value.Name] = ref
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
d.enter(loopTitle(x))
|
|
467
|
+
r.walkStmts(d, s, x.Body.List, depth)
|
|
468
|
+
d.leave()
|
|
469
|
+
case *ast.BlockStmt:
|
|
470
|
+
r.walkStmts(d, s, x.List, depth)
|
|
471
|
+
case *ast.LabeledStmt:
|
|
472
|
+
r.walkStmt(d, s, x.Stmt, depth)
|
|
473
|
+
case *ast.SwitchStmt:
|
|
474
|
+
if x.Init != nil {
|
|
475
|
+
r.walkStmt(d, s, x.Init, depth)
|
|
476
|
+
}
|
|
477
|
+
if x.Tag != nil {
|
|
478
|
+
r.callsIn(d, s, x.Tag, depth)
|
|
479
|
+
}
|
|
480
|
+
r.walkCases(d, s, x.Body.List, switchTitle(x.Tag), depth)
|
|
481
|
+
case *ast.TypeSwitchStmt:
|
|
482
|
+
if x.Init != nil {
|
|
483
|
+
r.walkStmt(d, s, x.Init, depth)
|
|
484
|
+
}
|
|
485
|
+
r.walkStmt(d, s, x.Assign, depth)
|
|
486
|
+
r.walkCases(d, s, x.Body.List, typeSwitchTitle(x.Assign), depth)
|
|
487
|
+
case *ast.SelectStmt:
|
|
488
|
+
r.walkStmts(d, s, x.Body.List, depth)
|
|
489
|
+
case *ast.CaseClause:
|
|
490
|
+
for _, expr := range x.List {
|
|
491
|
+
r.callsIn(d, s, expr, depth)
|
|
492
|
+
}
|
|
493
|
+
r.walkStmts(d, s, x.Body, depth)
|
|
494
|
+
case *ast.CommClause:
|
|
495
|
+
if x.Comm != nil {
|
|
496
|
+
r.walkStmt(d, s, x.Comm, depth)
|
|
497
|
+
}
|
|
498
|
+
r.walkStmts(d, s, x.Body, depth)
|
|
499
|
+
default:
|
|
500
|
+
r.callsIn(d, s, stmt, depth)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// walkCases reads a switch as one choice, an arm per case. The title of an
|
|
505
|
+
// arm is what the case says, with the subject in front of it - `resp.Code is
|
|
506
|
+
// 200, 201`, `err is ErrNotFound` - and `default` is otherwise. The rest of
|
|
507
|
+
// the rule is the `if` rule: an arm is terminal when it returns, the choice is
|
|
508
|
+
// drawn only if some arm has a hop in it, and a choice every arm of which
|
|
509
|
+
// leaves loses the marks.
|
|
510
|
+
func (r *flowReader) walkCases(d *flowDraft, s *scope, clauses []ast.Stmt, title func([]ast.Expr) string, depth int) {
|
|
511
|
+
var branches []catalog.AltBranch
|
|
512
|
+
titles := map[string]bool{}
|
|
513
|
+
drew := false
|
|
514
|
+
sawDefault := false
|
|
515
|
+
|
|
516
|
+
for _, stmt := range clauses {
|
|
517
|
+
clause, ok := stmt.(*ast.CaseClause)
|
|
518
|
+
if !ok {
|
|
519
|
+
continue
|
|
520
|
+
}
|
|
521
|
+
for _, expr := range clause.List {
|
|
522
|
+
r.callsIn(d, s, expr, depth)
|
|
523
|
+
}
|
|
524
|
+
d.push()
|
|
525
|
+
r.walkStmts(d, s, clause.Body, depth)
|
|
526
|
+
steps := d.pop()
|
|
527
|
+
drew = drew || len(steps) > 0
|
|
528
|
+
|
|
529
|
+
name := "otherwise"
|
|
530
|
+
if clause.List != nil {
|
|
531
|
+
name = title(clause.List)
|
|
532
|
+
} else {
|
|
533
|
+
sawDefault = true
|
|
534
|
+
}
|
|
535
|
+
terminal := len(clause.Body) > 0 && isReturn(clause.Body[len(clause.Body)-1])
|
|
536
|
+
branches = append(branches, catalog.AltBranch{Title: uniqueTitle(name, titles), Steps: steps, Terminal: terminal})
|
|
537
|
+
}
|
|
538
|
+
if !drew {
|
|
539
|
+
return
|
|
540
|
+
}
|
|
541
|
+
if !sawDefault {
|
|
542
|
+
branches = append(branches, catalog.AltBranch{Title: uniqueTitle("otherwise", titles), Steps: catalog.FlowNodes{}})
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
all := true
|
|
546
|
+
for _, branch := range branches {
|
|
547
|
+
if !branch.Terminal {
|
|
548
|
+
all = false
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if all {
|
|
552
|
+
for i := range branches {
|
|
553
|
+
branches[i].Terminal = false
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
d.addAlt(branches)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
func isReturn(stmt ast.Stmt) bool {
|
|
561
|
+
_, ok := stmt.(*ast.ReturnStmt)
|
|
562
|
+
|
|
563
|
+
return ok
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// switchTitle names an arm of a switch: with a tag, `tag is a, b`; without
|
|
567
|
+
// one the cases are conditions in their own right, joined by or.
|
|
568
|
+
func switchTitle(tag ast.Expr) func([]ast.Expr) string {
|
|
569
|
+
return func(list []ast.Expr) string {
|
|
570
|
+
parts := make([]string, 0, len(list))
|
|
571
|
+
for _, expr := range list {
|
|
572
|
+
parts = append(parts, types.ExprString(expr))
|
|
573
|
+
}
|
|
574
|
+
if tag == nil {
|
|
575
|
+
return strings.Join(parts, " or ")
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return types.ExprString(tag) + " is " + strings.Join(parts, ", ")
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// typeSwitchTitle names an arm of a type switch by what is being asked
|
|
583
|
+
// about: `e is *OrderPlaced, *OrderCancelled`.
|
|
584
|
+
func typeSwitchTitle(assign ast.Stmt) func([]ast.Expr) string {
|
|
585
|
+
subject := ""
|
|
586
|
+
var find func(ast.Node) bool
|
|
587
|
+
find = func(node ast.Node) bool {
|
|
588
|
+
if assert, ok := node.(*ast.TypeAssertExpr); ok && assert.Type == nil {
|
|
589
|
+
subject = types.ExprString(assert.X)
|
|
590
|
+
|
|
591
|
+
return false
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return true
|
|
595
|
+
}
|
|
596
|
+
ast.Inspect(assign, find)
|
|
597
|
+
|
|
598
|
+
return func(list []ast.Expr) string {
|
|
599
|
+
parts := make([]string, 0, len(list))
|
|
600
|
+
for _, expr := range list {
|
|
601
|
+
parts = append(parts, types.ExprString(expr))
|
|
602
|
+
}
|
|
603
|
+
if subject == "" {
|
|
604
|
+
return strings.Join(parts, ", ")
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return subject + " is " + strings.Join(parts, ", ")
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// walkIf reads an `if`, and its `else if` chain, as one choice. Each arm is
|
|
612
|
+
// read into its own list; the choice is kept only if some arm produced a
|
|
613
|
+
// step, because a frame around nothing tells the reader nothing. An arm that
|
|
614
|
+
// ends in a return is terminal - the flow does not continue past the alt on
|
|
615
|
+
// that path - and a chain with no final else gets an empty "otherwise" arm,
|
|
616
|
+
// which is the arm the steps after the alt follow.
|
|
617
|
+
func (r *flowReader) walkIf(d *flowDraft, s *scope, stmt *ast.IfStmt, depth int) {
|
|
618
|
+
var branches []catalog.AltBranch
|
|
619
|
+
titles := map[string]bool{}
|
|
620
|
+
drew := false
|
|
621
|
+
|
|
622
|
+
current := stmt
|
|
623
|
+
for {
|
|
624
|
+
if current.Init != nil {
|
|
625
|
+
r.walkStmt(d, s, current.Init, depth)
|
|
626
|
+
}
|
|
627
|
+
r.callsIn(d, s, current.Cond, depth)
|
|
628
|
+
|
|
629
|
+
d.push()
|
|
630
|
+
r.walkStmts(d, s, current.Body.List, depth)
|
|
631
|
+
steps := d.pop()
|
|
632
|
+
drew = drew || len(steps) > 0
|
|
633
|
+
branches = append(branches, catalog.AltBranch{
|
|
634
|
+
Title: uniqueTitle(types.ExprString(current.Cond), titles),
|
|
635
|
+
Steps: steps,
|
|
636
|
+
Terminal: endsWithReturn(current.Body),
|
|
637
|
+
})
|
|
638
|
+
|
|
639
|
+
if current.Else == nil {
|
|
640
|
+
branches = append(branches, catalog.AltBranch{Title: uniqueTitle("otherwise", titles), Steps: catalog.FlowNodes{}})
|
|
641
|
+
|
|
642
|
+
break
|
|
643
|
+
}
|
|
644
|
+
if next, ok := current.Else.(*ast.IfStmt); ok {
|
|
645
|
+
current = next
|
|
646
|
+
|
|
647
|
+
continue
|
|
648
|
+
}
|
|
649
|
+
block, ok := current.Else.(*ast.BlockStmt)
|
|
650
|
+
if !ok {
|
|
651
|
+
break
|
|
652
|
+
}
|
|
653
|
+
d.push()
|
|
654
|
+
r.walkStmts(d, s, block.List, depth)
|
|
655
|
+
steps = d.pop()
|
|
656
|
+
drew = drew || len(steps) > 0
|
|
657
|
+
branches = append(branches, catalog.AltBranch{
|
|
658
|
+
Title: uniqueTitle("otherwise", titles),
|
|
659
|
+
Steps: steps,
|
|
660
|
+
Terminal: endsWithReturn(block),
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
break
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if !drew {
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// A choice every arm of which ends the flow is not a choice about what
|
|
671
|
+
// comes next: nothing does, and the catalog refuses to draw an alt whose
|
|
672
|
+
// branches all leave. The arms stay; the mark comes off.
|
|
673
|
+
all := true
|
|
674
|
+
for _, branch := range branches {
|
|
675
|
+
if !branch.Terminal {
|
|
676
|
+
all = false
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if all {
|
|
680
|
+
for i := range branches {
|
|
681
|
+
branches[i].Terminal = false
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
d.addAlt(branches)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// callsIn contributes the calls of one node, in the order they are written.
|
|
689
|
+
func (r *flowReader) callsIn(d *flowDraft, s *scope, node ast.Node, depth int) {
|
|
690
|
+
if node == nil {
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
for _, site := range callSitesIn(node) {
|
|
694
|
+
r.call(d, s, site, depth)
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
func uniqueTitle(title string, seen map[string]bool) string {
|
|
699
|
+
out := title
|
|
700
|
+
for n := 2; seen[out]; n++ {
|
|
701
|
+
out = title + " (" + strconv.Itoa(n) + ")"
|
|
702
|
+
}
|
|
703
|
+
seen[out] = true
|
|
704
|
+
|
|
705
|
+
return out
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// endsWithReturn is what makes a branch terminal: control leaves the function
|
|
709
|
+
// at the end of the block, so nothing written after the `if` runs on it.
|
|
710
|
+
func endsWithReturn(block *ast.BlockStmt) bool {
|
|
711
|
+
if block == nil || len(block.List) == 0 {
|
|
712
|
+
return false
|
|
713
|
+
}
|
|
714
|
+
_, ok := block.List[len(block.List)-1].(*ast.ReturnStmt)
|
|
715
|
+
|
|
716
|
+
return ok
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
func (r *flowReader) call(d *flowDraft, s *scope, site callSite, depth int) {
|
|
720
|
+
if ident, ok := site.call.Fun.(*ast.Ident); ok && ident.Name == "append" {
|
|
721
|
+
// Preserve an event while it is collected into a variadic slice before
|
|
722
|
+
// Save(ctx, aggregate, events...). The slice is still carrying that
|
|
723
|
+
// domain fact even though its static type is []event.Event.
|
|
724
|
+
for _, arg := range site.call.Args[1:] {
|
|
725
|
+
name, ok := arg.(*ast.Ident)
|
|
726
|
+
if !ok {
|
|
727
|
+
continue
|
|
728
|
+
}
|
|
729
|
+
ref, tracked := s.vars[name.Name]
|
|
730
|
+
if tracked && ref.event {
|
|
731
|
+
bind(s, site, []domainRef{ref})
|
|
732
|
+
break
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
selector, ok := site.call.Fun.(*ast.SelectorExpr)
|
|
739
|
+
if !ok {
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
method := selector.Sel.Name
|
|
743
|
+
|
|
744
|
+
switch x := selector.X.(type) {
|
|
745
|
+
case *ast.SelectorExpr:
|
|
746
|
+
// uc.<port>.<Method>(...): the only shape that is a hop.
|
|
747
|
+
receiver, ok := x.X.(*ast.Ident)
|
|
748
|
+
if !ok || receiver.Name != s.recv {
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
r.portCall(d, s, x.Sel.Name, method, site, depth)
|
|
752
|
+
|
|
753
|
+
case *ast.Ident:
|
|
754
|
+
switch {
|
|
755
|
+
case x.Name == s.recv:
|
|
756
|
+
// uc.<helper>(...): still this use case, in another method. The
|
|
757
|
+
// writing a use case does in a helper is writing it does.
|
|
758
|
+
if helper := s.pkg.methods(s.recvType)[method]; helper != nil {
|
|
759
|
+
r.walkBody(d, &scope{
|
|
760
|
+
pkg: s.pkg,
|
|
761
|
+
key: s.key,
|
|
762
|
+
fields: s.fields,
|
|
763
|
+
imports: s.imports,
|
|
764
|
+
vars: map[string]domainRef{},
|
|
765
|
+
recv: receiverIdent(helper),
|
|
766
|
+
recvType: s.recvType,
|
|
767
|
+
}, helper, depth)
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
case s.imports[x.Name] != "":
|
|
771
|
+
// session.Start(...): a domain constructor. Not a hop - nothing
|
|
772
|
+
// left the service - but its signature says what came back.
|
|
773
|
+
bind(s, site, r.resultsOfFunc(s.imports[x.Name], method))
|
|
774
|
+
|
|
775
|
+
default:
|
|
776
|
+
// sess.Revoke(...): a method on something the domain handed over.
|
|
777
|
+
if ref, ok := s.vars[x.Name]; ok {
|
|
778
|
+
bind(s, site, r.resultsOfMethod(ref, method))
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// portCall turns a call on a port into a hop, and decides whose lane it lands
|
|
785
|
+
// in. Everything a use case reaches, it reaches through a field of UseCase.
|
|
786
|
+
func (r *flowReader) portCall(d *flowDraft, s *scope, field, method string, site callSite, depth int) {
|
|
787
|
+
declared := s.fields[field]
|
|
788
|
+
if declared == "" || strings.HasPrefix(declared, "func") {
|
|
789
|
+
// A clock and an id generator are ports too, but nothing is at the
|
|
790
|
+
// other end of them.
|
|
791
|
+
return
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
source, line := s.pkg.position(site.call.Pos())
|
|
795
|
+
|
|
796
|
+
// A port that is, or is adapted over, another service's generated client.
|
|
797
|
+
if hops := r.clientCalls(s, declared, method); len(hops) > 0 {
|
|
798
|
+
for _, hop := range hops {
|
|
799
|
+
r.rpcHop(d, hop, at(source, line))
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// A port this use case declares, bound in assembly to another use case:
|
|
806
|
+
// by method when the adapter reaches several, by port otherwise.
|
|
807
|
+
target, ok := r.bindings[s.key+"."+declared+"."+method]
|
|
808
|
+
if !ok {
|
|
809
|
+
target, ok = r.bindings[s.key+"."+declared]
|
|
810
|
+
}
|
|
811
|
+
if ok {
|
|
812
|
+
r.useCaseHop(d, target, "Port `"+declared+"`, bound at assembly to the "+operationName(target)+" use case.", at(source, line), depth)
|
|
813
|
+
|
|
814
|
+
return
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// A field holding another use case outright, which is what a policy does.
|
|
818
|
+
if target, ok := useCaseSelector(declared, s.imports); ok {
|
|
819
|
+
r.useCaseHop(d, target, "", at(source, line), depth)
|
|
820
|
+
|
|
821
|
+
return
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// A consumer-owned interface bound directly to a concrete adapter by Wire
|
|
825
|
+
// is local work. There is no architectural lane to draw for password
|
|
826
|
+
// hashing, encoding, or another in-process mechanism.
|
|
827
|
+
if r.wireBoundPorts[s.key+"."+declared] {
|
|
828
|
+
return
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// A port of the domain: the store is at the other end of it.
|
|
832
|
+
aggregate, name, ok := domainSelector(declared, s.imports)
|
|
833
|
+
if !ok {
|
|
834
|
+
selector, _, _ := strings.Cut(strings.TrimPrefix(declared, "*"), ".")
|
|
835
|
+
if applicationSupportImport(s.imports[selector]) {
|
|
836
|
+
// Application services such as password hashing are local work,
|
|
837
|
+
// not a hop to another participant in the architecture flow.
|
|
838
|
+
return
|
|
839
|
+
}
|
|
840
|
+
r.b.Warn(s.key, s.pkg.dir+": port `"+field+" "+declared+"` is neither a domain port nor a use case; its calls are left out of the flow")
|
|
841
|
+
|
|
842
|
+
return
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
d.add(catalog.Step{
|
|
846
|
+
From: r.opts.svcID,
|
|
847
|
+
To: r.storeLane(d),
|
|
848
|
+
Kind: catalog.StepCall,
|
|
849
|
+
Label: method,
|
|
850
|
+
Line: at(source, line),
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
// The events a change produced are handed to the repository along with the
|
|
854
|
+
// aggregate, so a port call carrying one is where the fact leaves for the
|
|
855
|
+
// bus. What carries it there - an outbox, a relay - is the adapter's
|
|
856
|
+
// business and is not a step the source can show.
|
|
857
|
+
for _, arg := range site.call.Args {
|
|
858
|
+
ident, ok := arg.(*ast.Ident)
|
|
859
|
+
if !ok {
|
|
860
|
+
continue
|
|
861
|
+
}
|
|
862
|
+
ref, tracked := s.vars[ident.Name]
|
|
863
|
+
if !tracked || !ref.event {
|
|
864
|
+
continue
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
id := eventID(aggregateID(r.opts.svcID, ref.aggregate), ref.name)
|
|
868
|
+
d.add(catalog.Step{
|
|
869
|
+
From: r.opts.svcID,
|
|
870
|
+
To: d.laneID(r.busLane()),
|
|
871
|
+
Kind: catalog.StepEvent,
|
|
872
|
+
Ref: id,
|
|
873
|
+
Label: ref.name,
|
|
874
|
+
Line: at(source, line),
|
|
875
|
+
})
|
|
876
|
+
r.referenced[id] = true
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
bind(s, site, r.resultsOfPortMethod(aggregate, name, method))
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// rpcHop is a call to another service, on the lane the manifest gives its
|
|
883
|
+
// package. The call is also recorded for the service's consumes, sourced from
|
|
884
|
+
// the generated client it was read off, so the step's ref resolves without
|
|
885
|
+
// anybody vendoring the proto twice.
|
|
886
|
+
func (r *flowReader) rpcHop(d *flowDraft, hop rpcHop, line string) {
|
|
887
|
+
id := hop.client.methods[hop.method]
|
|
888
|
+
lane, peer, status := r.peerLane(d, hop.client)
|
|
889
|
+
|
|
890
|
+
// The label is the operation as the contract names it - the rpc, or the
|
|
891
|
+
// operationId - rather than the Go method the client offers it under:
|
|
892
|
+
// GetUserWithResponse is how the client is called, getUser is what runs.
|
|
893
|
+
d.add(catalog.Step{
|
|
894
|
+
From: r.opts.svcID,
|
|
895
|
+
To: lane,
|
|
896
|
+
Kind: catalog.StepRPC,
|
|
897
|
+
Ref: id,
|
|
898
|
+
Label: id[strings.LastIndex(id, "/")+1:],
|
|
899
|
+
Status: status,
|
|
900
|
+
Line: line,
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
if _, seen := r.calls[id]; !seen {
|
|
904
|
+
r.calls[id] = catalog.RpcCall{ID: id, Peer: peer, Status: status, Source: hop.client.source}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// useCaseHop is a call into another use case of the same service: a message to
|
|
909
|
+
// itself, and then that use case's own steps.
|
|
910
|
+
func (r *flowReader) useCaseHop(d *flowDraft, target, note, line string, depth int) {
|
|
911
|
+
d.add(catalog.Step{
|
|
912
|
+
From: r.opts.svcID,
|
|
913
|
+
To: r.opts.svcID,
|
|
914
|
+
Kind: catalog.StepCall,
|
|
915
|
+
Label: operationName(target),
|
|
916
|
+
Note: note,
|
|
917
|
+
Line: line,
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
r.walkUseCase(d, target, depth+1)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// ---------------------------------------------------------------------------
|
|
924
|
+
// following a value back to its type
|
|
925
|
+
// ---------------------------------------------------------------------------
|
|
926
|
+
|
|
927
|
+
// resultsOfFunc reads the results of a package-level function of a domain
|
|
928
|
+
// package: `func Start(...) (*Session, event.SessionStarted, error)`.
|
|
929
|
+
func (r *flowReader) resultsOfFunc(importPath, name string) []domainRef {
|
|
930
|
+
aggregate, ok := domainPackage(importPath)
|
|
931
|
+
if !ok {
|
|
932
|
+
return nil
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
pkg := r.domainPkg(aggregate)
|
|
936
|
+
if pkg == nil {
|
|
937
|
+
return nil
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
for _, file := range pkg.files {
|
|
941
|
+
for _, decl := range file.Decls {
|
|
942
|
+
fn, ok := decl.(*ast.FuncDecl)
|
|
943
|
+
if !ok || fn.Recv != nil || fn.Name.Name != name {
|
|
944
|
+
continue
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
return r.resultRefs(pkg, aggregate, fn.Type)
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
return nil
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// resultsOfMethod reads the results of a method on a domain type: what
|
|
955
|
+
// `sess.Revoke(...)` gives back.
|
|
956
|
+
func (r *flowReader) resultsOfMethod(ref domainRef, name string) []domainRef {
|
|
957
|
+
pkg := r.domainPkg(ref.aggregate)
|
|
958
|
+
if pkg == nil {
|
|
959
|
+
return nil
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
fn := pkg.methods(ref.name)[name]
|
|
963
|
+
if fn == nil {
|
|
964
|
+
return nil
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return r.resultRefs(pkg, ref.aggregate, fn.Type)
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// resultsOfPortMethod reads the results of one method of a domain port:
|
|
971
|
+
// `ByID(ctx, id) (*Session, error)` is how a use case comes to hold a Session.
|
|
972
|
+
func (r *flowReader) resultsOfPortMethod(aggregate, port, name string) []domainRef {
|
|
973
|
+
pkg := r.domainPkg(aggregate)
|
|
974
|
+
if pkg == nil {
|
|
975
|
+
return nil
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
for _, file := range pkg.files {
|
|
979
|
+
for _, decl := range file.Decls {
|
|
980
|
+
gen, ok := decl.(*ast.GenDecl)
|
|
981
|
+
if !ok {
|
|
982
|
+
continue
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
for _, spec := range gen.Specs {
|
|
986
|
+
typeSpec, ok := spec.(*ast.TypeSpec)
|
|
987
|
+
if !ok || typeSpec.Name.Name != port {
|
|
988
|
+
continue
|
|
989
|
+
}
|
|
990
|
+
iface, ok := typeSpec.Type.(*ast.InterfaceType)
|
|
991
|
+
if !ok || iface.Methods == nil {
|
|
992
|
+
continue
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
for _, method := range iface.Methods.List {
|
|
996
|
+
fn, ok := method.Type.(*ast.FuncType)
|
|
997
|
+
if !ok || len(method.Names) == 0 || method.Names[0].Name != name {
|
|
998
|
+
continue
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return r.resultRefs(pkg, aggregate, fn)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
return nil
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// resultRefs turns a result list into what each position holds, by position, so
|
|
1011
|
+
// that the names on the left of an assignment can be paired with it. A result
|
|
1012
|
+
// this cannot place - a string, an error - leaves a hole rather than shifting
|
|
1013
|
+
// everything after it along.
|
|
1014
|
+
func (r *flowReader) resultRefs(pkg *pkg, aggregate string, fn *ast.FuncType) []domainRef {
|
|
1015
|
+
if fn == nil || fn.Results == nil {
|
|
1016
|
+
return nil
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
imports := importsOf(pkg)
|
|
1020
|
+
|
|
1021
|
+
var out []domainRef
|
|
1022
|
+
for _, result := range fn.Results.List {
|
|
1023
|
+
ref := domainRef{}
|
|
1024
|
+
|
|
1025
|
+
name := strings.TrimPrefix(strings.TrimPrefix(types.ExprString(result.Type), "[]"), "*")
|
|
1026
|
+
name = strings.TrimPrefix(name, "[]")
|
|
1027
|
+
|
|
1028
|
+
if selector, typeName, cut := strings.Cut(name, "."); cut {
|
|
1029
|
+
if agg, isEvent := eventPackage(imports[selector]); isEvent {
|
|
1030
|
+
ref = domainRef{aggregate: agg, name: typeName, event: true}
|
|
1031
|
+
}
|
|
1032
|
+
} else if exported(name) {
|
|
1033
|
+
ref = domainRef{aggregate: aggregate, name: name}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// One entry per name, because `a, b Session` is two results.
|
|
1037
|
+
count := len(result.Names)
|
|
1038
|
+
if count == 0 {
|
|
1039
|
+
count = 1
|
|
1040
|
+
}
|
|
1041
|
+
for range count {
|
|
1042
|
+
out = append(out, ref)
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return out
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
func bind(s *scope, site callSite, results []domainRef) {
|
|
1050
|
+
for i, expr := range site.lhs {
|
|
1051
|
+
if i >= len(results) || results[i].name == "" {
|
|
1052
|
+
continue
|
|
1053
|
+
}
|
|
1054
|
+
if ident, ok := expr.(*ast.Ident); ok && ident.Name != "_" {
|
|
1055
|
+
s.vars[ident.Name] = results[i]
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// ---------------------------------------------------------------------------
|
|
1061
|
+
// the draft a flow is built up in
|
|
1062
|
+
// ---------------------------------------------------------------------------
|
|
1063
|
+
|
|
1064
|
+
type flowDraft struct {
|
|
1065
|
+
lanes []catalog.Participant
|
|
1066
|
+
steps catalog.FlowNodes
|
|
1067
|
+
// sinks is where the next node goes: the arm of an alt being read, or,
|
|
1068
|
+
// with nothing pushed, the flow's own list.
|
|
1069
|
+
sinks []*catalog.FlowNodes
|
|
1070
|
+
// n numbers every node, step or frame, so an id is unique wherever the
|
|
1071
|
+
// node sits.
|
|
1072
|
+
n int
|
|
1073
|
+
seen map[string]bool
|
|
1074
|
+
// loops is the loops enclosing whatever is being read right now, outermost
|
|
1075
|
+
// first. A use case called once per session is read once, and every step it
|
|
1076
|
+
// contributes happens once per session; the fact belongs to the steps, not
|
|
1077
|
+
// to the call that got there.
|
|
1078
|
+
loops []string
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
func (d *flowDraft) enter(loop string) { d.loops = append(d.loops, loop) }
|
|
1082
|
+
func (d *flowDraft) leave() { d.loops = d.loops[:len(d.loops)-1] }
|
|
1083
|
+
|
|
1084
|
+
// note puts the loops in front of whatever the step had to say for itself.
|
|
1085
|
+
func (d *flowDraft) note(own string) string {
|
|
1086
|
+
var loops []string
|
|
1087
|
+
for _, loop := range d.loops {
|
|
1088
|
+
if loop == "" || contains(loops, loop) {
|
|
1089
|
+
continue
|
|
1090
|
+
}
|
|
1091
|
+
loops = append(loops, loop)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
out := ""
|
|
1095
|
+
if len(loops) > 0 {
|
|
1096
|
+
out = strings.Join(loops, ", ") + "."
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return strings.TrimSpace(out + " " + own)
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
func contains(list []string, value string) bool {
|
|
1103
|
+
for _, existing := range list {
|
|
1104
|
+
if existing == value {
|
|
1105
|
+
return true
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return false
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
// lane declares a participant once, in the order it was first needed. That
|
|
1113
|
+
// order is the order of the lanes in every picture drawn from this.
|
|
1114
|
+
func (d *flowDraft) lane(p catalog.Participant) string {
|
|
1115
|
+
for _, existing := range d.lanes {
|
|
1116
|
+
if existing.ID == p.ID {
|
|
1117
|
+
return p.ID
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
d.lanes = append(d.lanes, p)
|
|
1121
|
+
|
|
1122
|
+
return p.ID
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
func (d *flowDraft) laneID(p catalog.Participant) string { return d.lane(p) }
|
|
1126
|
+
|
|
1127
|
+
func newDraft() *flowDraft { return &flowDraft{seen: map[string]bool{}} }
|
|
1128
|
+
|
|
1129
|
+
func (d *flowDraft) sink() *catalog.FlowNodes {
|
|
1130
|
+
if len(d.sinks) == 0 {
|
|
1131
|
+
return &d.steps
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
return d.sinks[len(d.sinks)-1]
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
func (d *flowDraft) push() { d.sinks = append(d.sinks, &catalog.FlowNodes{}) }
|
|
1138
|
+
|
|
1139
|
+
func (d *flowDraft) pop() catalog.FlowNodes {
|
|
1140
|
+
top := d.sinks[len(d.sinks)-1]
|
|
1141
|
+
d.sinks = d.sinks[:len(d.sinks)-1]
|
|
1142
|
+
|
|
1143
|
+
return *top
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
func (d *flowDraft) add(step catalog.Step) {
|
|
1147
|
+
d.n++
|
|
1148
|
+
step.Type = "step"
|
|
1149
|
+
step.ID = "s" + strconv.Itoa(d.n)
|
|
1150
|
+
step.Note = d.note(step.Note)
|
|
1151
|
+
// Nothing here has been watched running. `declared` is the whole of what
|
|
1152
|
+
// reading source can claim - unless the step already says less.
|
|
1153
|
+
if step.Status == "" {
|
|
1154
|
+
step.Status = catalog.StatusDeclared
|
|
1155
|
+
}
|
|
1156
|
+
sink := d.sink()
|
|
1157
|
+
*sink = append(*sink, &step)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
func (d *flowDraft) addAlt(branches []catalog.AltBranch) {
|
|
1161
|
+
d.n++
|
|
1162
|
+
sink := d.sink()
|
|
1163
|
+
*sink = append(*sink, &catalog.Alt{Type: "alt", ID: "alt" + strconv.Itoa(d.n), Branches: branches})
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
func (r *flowReader) serviceLane() catalog.Participant {
|
|
1167
|
+
context := r.opts.context
|
|
1168
|
+
|
|
1169
|
+
return catalog.Participant{ID: r.opts.svcID, Kind: catalog.ParticipantService, Context: &context}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
func (r *flowReader) busLane() catalog.Participant {
|
|
1173
|
+
return catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// storeLane is the lane persistence lands in, and the service's own when the
|
|
1177
|
+
// manifest does not name a store. A call on a repository happened either way;
|
|
1178
|
+
// what is unknown is only where it landed, and saying so once is better than
|
|
1179
|
+
// inventing a database nobody named.
|
|
1180
|
+
//
|
|
1181
|
+
// The id is `<service>-<store>` rather than the store's own `<service>.<store>`
|
|
1182
|
+
// because a participant that is not a service has to be a bare name: a dot is
|
|
1183
|
+
// how the diagram model spells containment, and a lane called `auth.auth.pg`
|
|
1184
|
+
// is read there as something inside the service rather than beside it.
|
|
1185
|
+
func (r *flowReader) storeLane(d *flowDraft) string {
|
|
1186
|
+
if r.opts.store == "" {
|
|
1187
|
+
if !r.warnedStore {
|
|
1188
|
+
r.b.Warn(r.opts.svcID, "no store named in the options, so repository calls stay on the service's own lane")
|
|
1189
|
+
r.warnedStore = true
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return r.opts.svcID
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
context := r.opts.context
|
|
1196
|
+
|
|
1197
|
+
return d.lane(catalog.Participant{
|
|
1198
|
+
ID: r.opts.service + "-" + r.opts.store,
|
|
1199
|
+
Kind: catalog.ParticipantStore,
|
|
1200
|
+
Context: &context,
|
|
1201
|
+
})
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// ---------------------------------------------------------------------------
|
|
1205
|
+
// syntax helpers
|
|
1206
|
+
// ---------------------------------------------------------------------------
|
|
1207
|
+
|
|
1208
|
+
// callSite is one call and the names it was assigned to.
|
|
1209
|
+
type callSite struct {
|
|
1210
|
+
call *ast.CallExpr
|
|
1211
|
+
lhs []ast.Expr
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// callSites lists the calls of a body in source order.
|
|
1215
|
+
//
|
|
1216
|
+
// The order is the flow. ast.Inspect walks children in the order they are
|
|
1217
|
+
// written, so a call in an `if` initialiser comes before the block it guards,
|
|
1218
|
+
// which is what a reader of the code sees too.
|
|
1219
|
+
func callSites(fn *ast.FuncDecl) []callSite {
|
|
1220
|
+
if fn == nil || fn.Body == nil {
|
|
1221
|
+
return nil
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
return callSitesIn(fn.Body)
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// callSitesIn lists the calls under one node, in source order.
|
|
1228
|
+
func callSitesIn(node ast.Node) []callSite {
|
|
1229
|
+
assigned := map[*ast.CallExpr][]ast.Expr{}
|
|
1230
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
1231
|
+
assign, ok := n.(*ast.AssignStmt)
|
|
1232
|
+
if !ok || len(assign.Rhs) != 1 {
|
|
1233
|
+
return true
|
|
1234
|
+
}
|
|
1235
|
+
if call, ok := assign.Rhs[0].(*ast.CallExpr); ok {
|
|
1236
|
+
assigned[call] = assign.Lhs
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return true
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
var out []callSite
|
|
1243
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
1244
|
+
if call, ok := n.(*ast.CallExpr); ok {
|
|
1245
|
+
out = append(out, callSite{call: call, lhs: assigned[call]})
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return true
|
|
1249
|
+
})
|
|
1250
|
+
|
|
1251
|
+
return out
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// loopTitle says that a step repeats, for the loop it sits in.
|
|
1255
|
+
//
|
|
1256
|
+
// A loop is a frame the catalog can hold, and this deliberately does not build
|
|
1257
|
+
// one: the frame would have to say what it repeats until, and the condition is
|
|
1258
|
+
// written for a compiler rather than for a reader. A note says the true part.
|
|
1259
|
+
func loopTitle(stmt ast.Stmt) string {
|
|
1260
|
+
switch node := stmt.(type) {
|
|
1261
|
+
case *ast.RangeStmt:
|
|
1262
|
+
return "inside a loop over `" + types.ExprString(node.X) + "`"
|
|
1263
|
+
case *ast.ForStmt:
|
|
1264
|
+
if node.Cond != nil {
|
|
1265
|
+
return "inside a loop, while `" + types.ExprString(node.Cond) + "`"
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
return "inside a loop"
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
return ""
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// useCaseFields reads the ports off `type UseCase struct`.
|
|
1275
|
+
func useCaseFields(pkg *pkg) map[string]string {
|
|
1276
|
+
for _, decl := range pkg.structs() {
|
|
1277
|
+
if decl.name == "UseCase" {
|
|
1278
|
+
return structFields(decl.fields)
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
return map[string]string{}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
func structFields(st *ast.StructType) map[string]string {
|
|
1286
|
+
out := map[string]string{}
|
|
1287
|
+
if st == nil || st.Fields == nil {
|
|
1288
|
+
return out
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
for _, field := range st.Fields.List {
|
|
1292
|
+
declared := types.ExprString(field.Type)
|
|
1293
|
+
for _, name := range field.Names {
|
|
1294
|
+
out[name.Name] = declared
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return out
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// importsOf maps the name a package's files refer to an import by - its alias,
|
|
1302
|
+
// or the last segment of its path - to the path itself.
|
|
1303
|
+
func importsOf(pkg *pkg) map[string]string {
|
|
1304
|
+
out := map[string]string{}
|
|
1305
|
+
|
|
1306
|
+
for _, file := range pkg.files {
|
|
1307
|
+
for _, spec := range file.Imports {
|
|
1308
|
+
importPath := strings.Trim(spec.Path.Value, `"`)
|
|
1309
|
+
|
|
1310
|
+
name := path.Base(importPath)
|
|
1311
|
+
if spec.Name != nil {
|
|
1312
|
+
name = spec.Name.Name
|
|
1313
|
+
}
|
|
1314
|
+
out[name] = importPath
|
|
1315
|
+
|
|
1316
|
+
// A feature-sliced domain commonly lives in .../user/domain while
|
|
1317
|
+
// declaring `package user`. Go binds the declared package name, not
|
|
1318
|
+
// the final path segment, so retain that logical name as well.
|
|
1319
|
+
if spec.Name == nil {
|
|
1320
|
+
if aggregate, ok := domainImport(importPath); ok {
|
|
1321
|
+
out[aggregate] = importPath
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
return out
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// domainPackage reads an import of the service's own domain back to the
|
|
1331
|
+
// aggregate it belongs to, and says no to everything else.
|
|
1332
|
+
func domainPackage(importPath string) (string, bool) {
|
|
1333
|
+
return domainImport(importPath)
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// eventPackage recognizes the event package below either supported domain
|
|
1337
|
+
// layout, plus a feature's integration-event package.
|
|
1338
|
+
func eventPackage(importPath string) (string, bool) {
|
|
1339
|
+
return eventImport(importPath)
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// domainSelector reads a field type like `session.Repository` back to the
|
|
1343
|
+
// aggregate and the port name.
|
|
1344
|
+
func domainSelector(declared string, imports map[string]string) (string, string, bool) {
|
|
1345
|
+
selector, name, found := strings.Cut(strings.TrimPrefix(declared, "*"), ".")
|
|
1346
|
+
if !found {
|
|
1347
|
+
return "", "", false
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
aggregate, ok := domainPackage(imports[selector])
|
|
1351
|
+
|
|
1352
|
+
return aggregate, name, ok
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// useCaseSelector reads `*end_after_credential_change.UseCase` back to the use
|
|
1356
|
+
// case key.
|
|
1357
|
+
func useCaseSelector(declared string, imports map[string]string) (string, bool) {
|
|
1358
|
+
selector, name, found := strings.Cut(strings.TrimPrefix(declared, "*"), ".")
|
|
1359
|
+
if !found || name != "UseCase" {
|
|
1360
|
+
return "", false
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
aggregate, useCaseName, found := useCaseImport(imports[selector])
|
|
1364
|
+
if !found {
|
|
1365
|
+
return "", false
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
return aggregate + "/" + useCaseName, true
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
func (r *flowReader) useCasePkg(key string) *pkg {
|
|
1372
|
+
if cached, ok := r.useCases[key]; ok {
|
|
1373
|
+
return cached
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
dir := r.layout.useCases[key]
|
|
1377
|
+
pkg, err := parsePkg(r.root, dir)
|
|
1378
|
+
if err != nil {
|
|
1379
|
+
r.b.Warn(key, dir+" could not be parsed; its steps are missing from every flow that runs it")
|
|
1380
|
+
pkg = nil
|
|
1381
|
+
}
|
|
1382
|
+
r.useCases[key] = pkg
|
|
1383
|
+
|
|
1384
|
+
return pkg
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
func (r *flowReader) domainPkg(aggregate string) *pkg {
|
|
1388
|
+
if cached, ok := r.domains[aggregate]; ok {
|
|
1389
|
+
return cached
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
pkg, err := parsePkg(r.root, r.layout.domains[aggregate])
|
|
1393
|
+
if err != nil {
|
|
1394
|
+
pkg = nil
|
|
1395
|
+
}
|
|
1396
|
+
r.domains[aggregate] = pkg
|
|
1397
|
+
|
|
1398
|
+
return pkg
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
func (r *flowReader) useCaseDoc(key string) string {
|
|
1402
|
+
pkg := r.useCasePkg(key)
|
|
1403
|
+
if pkg == nil {
|
|
1404
|
+
return ""
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
return operationDoc(r.root, r.layout.useCases[key], pkg)
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
func operationName(key string) string {
|
|
1411
|
+
_, name, _ := strings.Cut(key, "/")
|
|
1412
|
+
|
|
1413
|
+
return camel(name)
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// at is the `file:line` a reader can open. Written here rather than at every
|
|
1417
|
+
// call site so that a step without a position says nothing instead of ":0".
|
|
1418
|
+
func at(source string, line int) string {
|
|
1419
|
+
if source == "" || line == 0 {
|
|
1420
|
+
return ""
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
return source + ":" + strconv.Itoa(line)
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// sentence turns a slug into a name written the way a person would: one capital
|
|
1427
|
+
// at the front, and nothing else touched.
|
|
1428
|
+
func sentence(slug string) string {
|
|
1429
|
+
words := strings.Split(slug, "-")
|
|
1430
|
+
if len(words) == 0 || words[0] == "" {
|
|
1431
|
+
return slug
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
runes := []rune(words[0])
|
|
1435
|
+
if runes[0] >= 'a' && runes[0] <= 'z' {
|
|
1436
|
+
runes[0] = runes[0] - 'a' + 'A'
|
|
1437
|
+
}
|
|
1438
|
+
words[0] = string(runes)
|
|
1439
|
+
|
|
1440
|
+
return strings.Join(words, " ")
|
|
1441
|
+
}
|