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