@shortlink-org/portolan 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
// The same fact read out of a repository written in Rust, where sqlx puts the
|
|
4
|
-
// statement first and the values after it, one `bind` each:
|
|
5
|
-
//
|
|
6
|
-
// sqlx::query("INSERT INTO orders (id, customer_id, placed_at) VALUES ($1, $2, $3)")
|
|
7
|
-
// .bind(order.id)
|
|
8
|
-
// .bind(&order.customer_id)
|
|
9
|
-
// .bind(order.placed_at)
|
|
10
|
-
//
|
|
11
|
-
// or, in the checked macros, as arguments after the literal:
|
|
12
|
-
//
|
|
13
|
-
// sqlx::query!("INSERT INTO orders (id, customer_id) VALUES ($1, $2)", order.id, order.customer_id)
|
|
14
|
-
//
|
|
15
|
-
// Read off the text like the TypeScript one, for the same reason: the
|
|
16
|
-
// statement is a string literal, the values are expressions right after it,
|
|
17
|
-
// and each is a field chain from the aggregate, possibly borrowed, possibly
|
|
18
|
-
// wrapped in one conversion. Anything else is left unmapped.
|
|
19
|
-
|
|
20
|
-
import (
|
|
21
|
-
"os"
|
|
22
|
-
"path/filepath"
|
|
23
|
-
"regexp"
|
|
24
|
-
"strings"
|
|
25
|
-
|
|
26
|
-
"github.com/pgplex/pgparser/nodes"
|
|
27
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
func readMapsRust(root, repositoryDir, aggregate string, b *plugin.Builder) map[string]map[string]string {
|
|
31
|
-
out := map[string]map[string]string{}
|
|
32
|
-
dir := mapSourceDir(root, repositoryDir, aggregate)
|
|
33
|
-
entries, err := os.ReadDir(filepath.Join(root, filepath.FromSlash(dir)))
|
|
34
|
-
if err != nil {
|
|
35
|
-
return out
|
|
36
|
-
}
|
|
37
|
-
root_ := title(aggregate)
|
|
38
|
-
for _, entry := range entries {
|
|
39
|
-
name := entry.Name()
|
|
40
|
-
if entry.IsDir() || !strings.HasSuffix(name, ".rs") {
|
|
41
|
-
continue
|
|
42
|
-
}
|
|
43
|
-
source, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(dir), name))
|
|
44
|
-
if err != nil {
|
|
45
|
-
continue
|
|
46
|
-
}
|
|
47
|
-
for _, stmt := range rustInsertCalls(string(source)) {
|
|
48
|
-
table, columns, values := parseInsert(stmt.sql)
|
|
49
|
-
if table == "" {
|
|
50
|
-
continue
|
|
51
|
-
}
|
|
52
|
-
mapped := out[table]
|
|
53
|
-
if mapped == nil {
|
|
54
|
-
mapped = map[string]string{}
|
|
55
|
-
out[table] = mapped
|
|
56
|
-
}
|
|
57
|
-
numbers := placeholderNumbers(stmt.sql)
|
|
58
|
-
if numbers == nil || countParams(values) != len(numbers) {
|
|
59
|
-
if countParams(values) > 0 {
|
|
60
|
-
b.Warn(table, "the insert into "+table+" has placeholders this reader cannot number; its columns are left unmapped")
|
|
61
|
-
}
|
|
62
|
-
continue
|
|
63
|
-
}
|
|
64
|
-
seen := 0
|
|
65
|
-
for i, column := range columns {
|
|
66
|
-
if i >= len(values) {
|
|
67
|
-
break
|
|
68
|
-
}
|
|
69
|
-
if _, ok := values[i].(*nodes.ParamRef); !ok {
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
number := numbers[seen]
|
|
73
|
-
seen++
|
|
74
|
-
if number < 1 || number > len(stmt.args) {
|
|
75
|
-
b.Warn(table, "the insert refers to $"+itoa(number)+" but the call binds "+itoa(len(stmt.args))+" values; "+column+" is left unmapped")
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
field := fieldOfRust(stmt.args[number-1])
|
|
79
|
-
if field == "" {
|
|
80
|
-
continue
|
|
81
|
-
}
|
|
82
|
-
mapped[column] = root_ + "." + field
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return out
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// rustInsertCalls walks the source the way insertCalls does, with what Rust
|
|
91
|
-
// adds to the reading: raw strings, `r"…"` and `r#"…"#`, which is how a long
|
|
92
|
-
// statement is usually written; char literals, `'x'` and `'\n'`; and a
|
|
93
|
-
// lifetime, `'a`, which opens no literal at all and must not be read as one.
|
|
94
|
-
// A literal that holds an INSERT is a statement, and its values are the
|
|
95
|
-
// `.bind(…)` chain after the call that holds it, or the arguments after it
|
|
96
|
-
// inside a macro.
|
|
97
|
-
func rustInsertCalls(source string) []tsInsert {
|
|
98
|
-
var out []tsInsert
|
|
99
|
-
for i := 0; i < len(source); i++ {
|
|
100
|
-
c := source[i]
|
|
101
|
-
switch {
|
|
102
|
-
case c == '/' && i+1 < len(source) && source[i+1] == '/':
|
|
103
|
-
for i < len(source) && source[i] != '\n' {
|
|
104
|
-
i++
|
|
105
|
-
}
|
|
106
|
-
case c == '/' && i+1 < len(source) && source[i+1] == '*':
|
|
107
|
-
end := strings.Index(source[i+2:], "*/")
|
|
108
|
-
if end < 0 {
|
|
109
|
-
return out
|
|
110
|
-
}
|
|
111
|
-
i += 2 + end + 1
|
|
112
|
-
case c == '\'':
|
|
113
|
-
if end := charLiteralEnd(source, i); end > 0 {
|
|
114
|
-
i = end
|
|
115
|
-
}
|
|
116
|
-
case c == '"', c == 'r' && rawStringAt(source, i):
|
|
117
|
-
start, end, hashes := stringLiteral(source, i)
|
|
118
|
-
if end < 0 {
|
|
119
|
-
return out
|
|
120
|
-
}
|
|
121
|
-
sql := source[start:end]
|
|
122
|
-
i = end + hashes
|
|
123
|
-
if !strings.Contains(strings.ToUpper(sql), "INSERT INTO") {
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
j := skipSpace(source, i+1)
|
|
127
|
-
if j >= len(source) {
|
|
128
|
-
continue
|
|
129
|
-
}
|
|
130
|
-
if source[j] == ',' {
|
|
131
|
-
// A trailing comma inside the call is not the start of the
|
|
132
|
-
// macro's arguments: the next thing decides.
|
|
133
|
-
j = skipSpace(source, j+1)
|
|
134
|
-
}
|
|
135
|
-
if j >= len(source) {
|
|
136
|
-
continue
|
|
137
|
-
}
|
|
138
|
-
if source[j] == ')' {
|
|
139
|
-
// The builder shape: the values are bound one at a time after
|
|
140
|
-
// the call closes.
|
|
141
|
-
args, last := bindChain(source, j+1)
|
|
142
|
-
out = append(out, tsInsert{sql: sql, args: args})
|
|
143
|
-
i = last
|
|
144
|
-
} else {
|
|
145
|
-
// The macro shape: the values are the rest of the call.
|
|
146
|
-
args, close := argsUntilClose(source, j)
|
|
147
|
-
out = append(out, tsInsert{sql: sql, args: args})
|
|
148
|
-
i = close
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return out
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// rawStringAt says whether the `r` at i opens a raw string: `r"` or `r#…"`.
|
|
157
|
-
// An `r` inside a longer word, `order`, is not one, and the caller only asks
|
|
158
|
-
// about an `r` that is not preceded by an identifier character.
|
|
159
|
-
func rawStringAt(source string, i int) bool {
|
|
160
|
-
if i > 0 && isIdent(source[i-1]) {
|
|
161
|
-
return false
|
|
162
|
-
}
|
|
163
|
-
j := i + 1
|
|
164
|
-
for j < len(source) && source[j] == '#' {
|
|
165
|
-
j++
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return j < len(source) && source[j] == '"'
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// stringLiteral reads the literal opening at i and answers with the bounds of
|
|
172
|
-
// its content, the index of its closing quote, and how many `#` follow that
|
|
173
|
-
// quote for a raw string.
|
|
174
|
-
func stringLiteral(source string, i int) (int, int, int) {
|
|
175
|
-
if source[i] == '"' {
|
|
176
|
-
end := closingQuote(source, i)
|
|
177
|
-
|
|
178
|
-
return i + 1, end, 0
|
|
179
|
-
}
|
|
180
|
-
hashes := 0
|
|
181
|
-
j := i + 1
|
|
182
|
-
for source[j] == '#' {
|
|
183
|
-
hashes++
|
|
184
|
-
j++
|
|
185
|
-
}
|
|
186
|
-
closer := `"` + strings.Repeat("#", hashes)
|
|
187
|
-
end := strings.Index(source[j+1:], closer)
|
|
188
|
-
if end < 0 {
|
|
189
|
-
return 0, -1, 0
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return j + 1, j + 1 + end, hashes
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// charLiteralEnd is the index of the quote closing a char literal at i, or
|
|
196
|
-
// -1 when the quote at i is a lifetime's and closes nothing.
|
|
197
|
-
func charLiteralEnd(source string, i int) int {
|
|
198
|
-
if i+2 < len(source) && source[i+1] == '\\' {
|
|
199
|
-
end := strings.IndexByte(source[i+2:], '\'')
|
|
200
|
-
if end < 0 {
|
|
201
|
-
return -1
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return i + 2 + end
|
|
205
|
-
}
|
|
206
|
-
if i+2 < len(source) && source[i+2] == '\'' {
|
|
207
|
-
return i + 2
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return -1
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// argsUntilClose splits the arguments from i to the `)` that closes the call
|
|
214
|
-
// they are in, and answers with the index of that paren.
|
|
215
|
-
func argsUntilClose(source string, i int) ([]string, int) {
|
|
216
|
-
depth := 0
|
|
217
|
-
var quote byte
|
|
218
|
-
for j := i; j < len(source); j++ {
|
|
219
|
-
c := source[j]
|
|
220
|
-
if quote != 0 {
|
|
221
|
-
if c == '\\' {
|
|
222
|
-
j++
|
|
223
|
-
} else if c == quote {
|
|
224
|
-
quote = 0
|
|
225
|
-
}
|
|
226
|
-
continue
|
|
227
|
-
}
|
|
228
|
-
switch c {
|
|
229
|
-
case '"':
|
|
230
|
-
quote = c
|
|
231
|
-
case '[', '(', '{':
|
|
232
|
-
depth++
|
|
233
|
-
case ']', '}':
|
|
234
|
-
depth--
|
|
235
|
-
case ')':
|
|
236
|
-
if depth == 0 {
|
|
237
|
-
return splitTopLevel(source[i:j]), j
|
|
238
|
-
}
|
|
239
|
-
depth--
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return nil, len(source) - 1
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// bindChain reads `.bind(x)` calls one after another from i, stepping over
|
|
247
|
-
// whitespace and comments between them, and answers with what each was
|
|
248
|
-
// handed and the index it stopped at.
|
|
249
|
-
func bindChain(source string, i int) ([]string, int) {
|
|
250
|
-
var args []string
|
|
251
|
-
last := i - 1
|
|
252
|
-
for {
|
|
253
|
-
j := skipSpaceAndComments(source, i)
|
|
254
|
-
if !strings.HasPrefix(source[j:], ".bind(") {
|
|
255
|
-
return args, last
|
|
256
|
-
}
|
|
257
|
-
open := j + len(".bind")
|
|
258
|
-
close := matchingBracket(source, open)
|
|
259
|
-
if close < 0 {
|
|
260
|
-
return args, last
|
|
261
|
-
}
|
|
262
|
-
args = append(args, strings.TrimSpace(source[open+1:close]))
|
|
263
|
-
last = close
|
|
264
|
-
i = close + 1
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
func skipSpaceAndComments(source string, i int) int {
|
|
269
|
-
for {
|
|
270
|
-
i = skipSpace(source, i)
|
|
271
|
-
if strings.HasPrefix(source[i:], "//") {
|
|
272
|
-
for i < len(source) && source[i] != '\n' {
|
|
273
|
-
i++
|
|
274
|
-
}
|
|
275
|
-
continue
|
|
276
|
-
}
|
|
277
|
-
if strings.HasPrefix(source[i:], "/*") {
|
|
278
|
-
end := strings.Index(source[i+2:], "*/")
|
|
279
|
-
if end < 0 {
|
|
280
|
-
return len(source)
|
|
281
|
-
}
|
|
282
|
-
i += 2 + end + 2
|
|
283
|
-
continue
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return i
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
func isIdent(c byte) bool {
|
|
291
|
-
return c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
var rustChain = regexp.MustCompile(`^[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)+$`)
|
|
295
|
-
|
|
296
|
-
// fieldOfRust follows a bound value back to the field it carries, the way
|
|
297
|
-
// fieldOfTS does: `order.customer_id` is customer_id, `&order.customer_id`
|
|
298
|
-
// and `*order.version` the same, `order.status.as_str()` is status,
|
|
299
|
-
// `Some(order.placed_at)` and `i64::from(order.version)` are placed_at and
|
|
300
|
-
// version. A bare name, a literal, a call that makes its own value -
|
|
301
|
-
// `Uuid::new_v4()` - and a call over several values carry no field.
|
|
302
|
-
func fieldOfRust(expr string) string {
|
|
303
|
-
expr = strings.TrimSpace(expr)
|
|
304
|
-
expr = strings.TrimSuffix(expr, "?")
|
|
305
|
-
for strings.HasPrefix(expr, "&") || strings.HasPrefix(expr, "*") {
|
|
306
|
-
expr = strings.TrimSpace(strings.TrimLeft(expr, "&*"))
|
|
307
|
-
expr = strings.TrimSpace(strings.TrimPrefix(expr, "mut "))
|
|
308
|
-
}
|
|
309
|
-
for strings.HasSuffix(expr, ")") {
|
|
310
|
-
open := openingParen(expr)
|
|
311
|
-
if open <= 0 {
|
|
312
|
-
return ""
|
|
313
|
-
}
|
|
314
|
-
callee, inner := expr[:open], expr[open+1:len(expr)-1]
|
|
315
|
-
args := splitTopLevel(inner)
|
|
316
|
-
switch {
|
|
317
|
-
case rustChain.MatchString(callee) && len(args) == 0:
|
|
318
|
-
// A method on the value: the value is the chain before it.
|
|
319
|
-
expr = callee[:strings.LastIndex(callee, ".")]
|
|
320
|
-
case len(args) == 1:
|
|
321
|
-
// A conversion wrapped round the value.
|
|
322
|
-
return fieldOfRust(args[0])
|
|
323
|
-
default:
|
|
324
|
-
return ""
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if !rustChain.MatchString(expr) {
|
|
328
|
-
return ""
|
|
329
|
-
}
|
|
330
|
-
parts := strings.Split(expr, ".")
|
|
331
|
-
|
|
332
|
-
return strings.Join(parts[1:], ".")
|
|
333
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"testing"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
// The same rule as for Go and TypeScript, read off text: the chain from the
|
|
8
|
-
// aggregate, a borrow or a dereference in front of it, one conversion round
|
|
9
|
-
// it, a method on the value stripped.
|
|
10
|
-
func TestFieldOfRustFollowsTheChainBorrowsAndOneWrapper(t *testing.T) {
|
|
11
|
-
cases := map[string]string{
|
|
12
|
-
"order.id": "id",
|
|
13
|
-
"&order.customer_id": "customer_id",
|
|
14
|
-
"&mut order.lines": "lines",
|
|
15
|
-
"*order.version": "version",
|
|
16
|
-
"order.status.as_str()": "status",
|
|
17
|
-
"order.total.amount_minor": "total.amount_minor",
|
|
18
|
-
"order.total.currency.code()": "total.currency",
|
|
19
|
-
"Some(order.placed_at)": "placed_at",
|
|
20
|
-
"i64::from(order.version)": "version",
|
|
21
|
-
"order.basket_id.to_string()": "basket_id",
|
|
22
|
-
"&order.customer_id.as_deref()?": "customer_id",
|
|
23
|
-
"Uuid::new_v4()": "",
|
|
24
|
-
"serde_json::to_value(&event)?": "",
|
|
25
|
-
"combine(order.a, order.b)": "",
|
|
26
|
-
"TOPIC": "",
|
|
27
|
-
"1": "",
|
|
28
|
-
"\"literal\"": "",
|
|
29
|
-
"Utc::now()": "",
|
|
30
|
-
}
|
|
31
|
-
for in, want := range cases {
|
|
32
|
-
if got := fieldOfRust(in); got != want {
|
|
33
|
-
t.Errorf("fieldOfRust(%q) = %q, want %q", in, got, want)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func TestRustInsertCallsReadBothShapesAndStepOverLifetimes(t *testing.T) {
|
|
39
|
-
source := `
|
|
40
|
-
impl<'a> PostgresOrders<'a> {
|
|
41
|
-
async fn save(&self, tx: &mut Transaction<'_, Postgres>, order: &Order) -> Result<(), Error> {
|
|
42
|
-
sqlx::query("BEGIN").execute(&mut **tx).await?; // a literal before, which must not swallow what follows
|
|
43
|
-
let sep = ',';
|
|
44
|
-
sqlx::query(
|
|
45
|
-
r#"INSERT INTO orders (id, customer_id, version) VALUES ($1, $2, 1)"#,
|
|
46
|
-
)
|
|
47
|
-
.bind(order.id)
|
|
48
|
-
// the customer auth vouched for
|
|
49
|
-
.bind(&order.customer_id)
|
|
50
|
-
.execute(&mut **tx)
|
|
51
|
-
.await?;
|
|
52
|
-
let rows = sqlx::query("SELECT * FROM orders WHERE id = $1").bind(id).fetch_all(pool).await?;
|
|
53
|
-
sqlx::query!("INSERT INTO outbox (uuid, payload) VALUES ($1, $2)", Uuid::new_v4(), serde_json::to_value(&event)?)
|
|
54
|
-
.execute(&mut **tx)
|
|
55
|
-
.await?;
|
|
56
|
-
Ok(())
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
`
|
|
60
|
-
calls := rustInsertCalls(source)
|
|
61
|
-
if len(calls) != 2 {
|
|
62
|
-
t.Fatalf("found %d inserts, want 2", len(calls))
|
|
63
|
-
}
|
|
64
|
-
if len(calls[0].args) != 2 || calls[0].args[1] != "&order.customer_id" {
|
|
65
|
-
t.Fatalf("first insert args = %q", calls[0].args)
|
|
66
|
-
}
|
|
67
|
-
if len(calls[1].args) != 2 || calls[1].args[1] != "serde_json::to_value(&event)?" {
|
|
68
|
-
t.Fatalf("second insert args = %q", calls[1].args)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"go/parser"
|
|
6
|
-
"strings"
|
|
7
|
-
"testing"
|
|
8
|
-
|
|
9
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
func builderFor(t *testing.T) *plugin.Builder {
|
|
13
|
-
t.Helper()
|
|
14
|
-
|
|
15
|
-
return &plugin.Builder{}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
func expr(t *testing.T, src string) ast.Expr {
|
|
19
|
-
t.Helper()
|
|
20
|
-
|
|
21
|
-
parsed, err := parser.ParseExpr(src)
|
|
22
|
-
if err != nil {
|
|
23
|
-
t.Fatal(err)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return parsed
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// The column carries the value object, not the conversion applied to it on the
|
|
30
|
-
// way out - and the conversion is written both ways round.
|
|
31
|
-
func TestFieldOfFollowsBothCallShapes(t *testing.T) {
|
|
32
|
-
s := scope{packages: map[string]bool{"time": true, "pgtype": true, "uuid": true}}
|
|
33
|
-
cases := map[string]string{
|
|
34
|
-
"u.ID": "ID",
|
|
35
|
-
"u.Email.String()": "Email",
|
|
36
|
-
"nullable(s.RevokedAt)": "RevokedAt",
|
|
37
|
-
"pgtype.Text(u.Email)": "Email",
|
|
38
|
-
"time.Now()": "",
|
|
39
|
-
"uuid.NewString()": "",
|
|
40
|
-
"combine(u.A, u.B)": "",
|
|
41
|
-
"localVariable": "",
|
|
42
|
-
`"literal"`: "",
|
|
43
|
-
"q.ID()": "ID",
|
|
44
|
-
"q.Total().AmountMinor()": "Total",
|
|
45
|
-
"q.Total().Currency()": "Total",
|
|
46
|
-
"q.IssuedAt().Format(\"x\")": "IssuedAt",
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
for src, want := range cases {
|
|
50
|
-
if got := fieldOf(expr(t, src), s); got != want {
|
|
51
|
-
t.Errorf("fieldOf(%s) = %q, want %q", src, got, want)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Inside Save the aggregate is the parameter typed as the root, and nothing
|
|
57
|
-
// else: the loop variable over its lines carries the line's fields, and a
|
|
58
|
-
// mapping from those to the aggregate would be a claim about the wrong type.
|
|
59
|
-
func TestFieldOfReadsOnlyTheRootWhenTheFunctionNamesIt(t *testing.T) {
|
|
60
|
-
s := scope{packages: map[string]bool{}, roots: map[string]bool{"q": true}}
|
|
61
|
-
cases := map[string]string{
|
|
62
|
-
"q.ID()": "ID",
|
|
63
|
-
"q.BasketID()": "BasketID",
|
|
64
|
-
"l.SKU()": "",
|
|
65
|
-
"l.Quantity": "",
|
|
66
|
-
"raised.Name()": "",
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for src, want := range cases {
|
|
70
|
-
if got := fieldOf(expr(t, src), s); got != want {
|
|
71
|
-
t.Errorf("fieldOf(%s) = %q, want %q", src, got, want)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// The variable of a range loop stands for one element of what it walks, so a
|
|
77
|
-
// column written from it carries a field of the elements of a field.
|
|
78
|
-
func TestFieldOfFollowsARangeVariable(t *testing.T) {
|
|
79
|
-
s := scope{
|
|
80
|
-
packages: map[string]bool{},
|
|
81
|
-
roots: map[string]bool{"q": true},
|
|
82
|
-
aliases: map[string]ast.Expr{"l": expr(t, "q.Lines()"), "raised": expr(t, "events"), "l2": expr(t, "l2.Items")},
|
|
83
|
-
}
|
|
84
|
-
cases := map[string]string{
|
|
85
|
-
"l.SKU()": "Lines.SKU",
|
|
86
|
-
"l.UnitPrice().Currency()": "Lines.UnitPrice",
|
|
87
|
-
"raised.Name()": "",
|
|
88
|
-
"l2.Name": "",
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
for src, want := range cases {
|
|
92
|
-
if got := fieldOf(expr(t, src), s); got != want {
|
|
93
|
-
t.Errorf("fieldOf(%s) = %q, want %q", src, got, want)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Pricing keeps its fields unexported and writes rows off getters, and its
|
|
99
|
-
// lines are written from a loop variable over them.
|
|
100
|
-
func TestMapsThroughGetters(t *testing.T) {
|
|
101
|
-
b := builderFor(t)
|
|
102
|
-
mapped := readMaps("../../examples/shop/pricing", "internal/infrastructure/repository/quote", "quote", b)
|
|
103
|
-
|
|
104
|
-
want := map[string]map[string]string{
|
|
105
|
-
"quotes": {
|
|
106
|
-
"id": "Quote.ID",
|
|
107
|
-
"basket_id": "Quote.BasketID",
|
|
108
|
-
"total_minor": "Quote.Total",
|
|
109
|
-
"currency": "Quote.Total",
|
|
110
|
-
"state": "Quote.State",
|
|
111
|
-
"issued_at": "Quote.IssuedAt",
|
|
112
|
-
"expires_at": "Quote.ExpiresAt",
|
|
113
|
-
},
|
|
114
|
-
"quote_lines": {
|
|
115
|
-
"quote_id": "Quote.ID",
|
|
116
|
-
"sku": "Quote.Lines.SKU",
|
|
117
|
-
"quantity": "Quote.Lines.Quantity",
|
|
118
|
-
"unit_price_minor": "Quote.Lines.UnitPrice",
|
|
119
|
-
"currency": "Quote.Lines.UnitPrice",
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
for table, columns := range want {
|
|
123
|
-
for column, field := range columns {
|
|
124
|
-
if got := mapped[table][column]; got != field {
|
|
125
|
-
t.Errorf("%s.%s -> %q, want %q", table, column, got, field)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
if len(mapped[table]) != len(columns) {
|
|
129
|
-
t.Errorf("%s: mapped %v, want exactly %v", table, mapped[table], columns)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if len(mapped["outbox"]) != 0 {
|
|
133
|
-
t.Errorf("outbox: mapped %v, want nothing", mapped["outbox"])
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// The grammar port keeps a placeholder as a node with no number on it, so the
|
|
138
|
-
// numbers are read off the text. Taking the order of the nodes instead would
|
|
139
|
-
// be right for every statement written in ascending order and silently wrong
|
|
140
|
-
// for the one that is not.
|
|
141
|
-
func TestPlaceholderNumbersComeFromTheText(t *testing.T) {
|
|
142
|
-
got := placeholderNumbers("INSERT INTO t (a, b, c) VALUES ($2, $1, 1)")
|
|
143
|
-
if len(got) != 2 || got[0] != 2 || got[1] != 1 {
|
|
144
|
-
t.Errorf("numbers = %v, want [2 1]", got)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// A RETURNING clause is not a value being written.
|
|
148
|
-
got = placeholderNumbers("INSERT INTO t (a) VALUES ($1) RETURNING id")
|
|
149
|
-
if len(got) != 1 || got[0] != 1 {
|
|
150
|
-
t.Errorf("numbers = %v, want [1]", got)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// A statement is routinely assembled from a shared column list rather than
|
|
155
|
-
// written whole.
|
|
156
|
-
func TestStringValueFoldsConstantsAndConcatenation(t *testing.T) {
|
|
157
|
-
constants := map[string]string{"columns": "id, name"}
|
|
158
|
-
|
|
159
|
-
got, ok := stringValue(expr(t, "`INSERT INTO t (`+columns+`) VALUES ($1, $2)`"), constants)
|
|
160
|
-
if !ok || !strings.Contains(got, "id, name") {
|
|
161
|
-
t.Errorf("folded = %q ok=%v", got, ok)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Anything that cannot be folded is left alone rather than read half
|
|
165
|
-
// resolved.
|
|
166
|
-
if _, ok := stringValue(expr(t, "prefix + buildRest()"), constants); ok {
|
|
167
|
-
t.Error("a call is not a constant")
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// The whole point of the field: no rule over column names produces
|
|
172
|
-
// password_hash -> Password, and the statement that writes the row does.
|
|
173
|
-
func TestMapsAgainstTheRealService(t *testing.T) {
|
|
174
|
-
b := builderFor(t)
|
|
175
|
-
mapped := readMaps("../../examples/auth", "internal/user/infrastructure/repository", "user", b)
|
|
176
|
-
|
|
177
|
-
users := mapped["users"]
|
|
178
|
-
if users["password_hash"] != "User.Password" {
|
|
179
|
-
t.Errorf("password_hash -> %q", users["password_hash"])
|
|
180
|
-
}
|
|
181
|
-
if users["email"] != "User.Email" {
|
|
182
|
-
t.Errorf("email -> %q", users["email"])
|
|
183
|
-
}
|
|
184
|
-
// `version` is written as a literal 1, so nothing carries it from the
|
|
185
|
-
// aggregate and the column stays unmapped rather than guessed at.
|
|
186
|
-
if got, ok := users["version"]; ok {
|
|
187
|
-
t.Errorf("version should be unmapped, got %q", got)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// The session repository builds its statement from a constant column list and
|
|
192
|
-
// wraps one value in a conversion, so it exercises both readings at once.
|
|
193
|
-
func TestMapsThroughAConstantColumnList(t *testing.T) {
|
|
194
|
-
b := builderFor(t)
|
|
195
|
-
mapped := readMaps("../../examples/auth", "internal/session/infrastructure/repository", "session", b)
|
|
196
|
-
|
|
197
|
-
sessions := mapped["sessions"]
|
|
198
|
-
if sessions["revoked_at"] != "Session.RevokedAt" {
|
|
199
|
-
t.Errorf("revoked_at -> %q", sessions["revoked_at"])
|
|
200
|
-
}
|
|
201
|
-
if sessions["user_id"] != "Session.UserID" {
|
|
202
|
-
t.Errorf("user_id -> %q", sessions["user_id"])
|
|
203
|
-
}
|
|
204
|
-
}
|