@shortlink-org/portolan 0.1.0 → 0.2.1

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 +27 -10
  36. package/scripts/local-api.test.mjs +85 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +24 -3
  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 +17 -9
  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,174 +0,0 @@
1
- package main
2
-
3
- import (
4
- "go/ast"
5
- "go/token"
6
- "os"
7
- "path/filepath"
8
- "strconv"
9
- "strings"
10
-
11
- "github.com/shortlink-org/portolan/plugins/openapi"
12
- )
13
-
14
- // An HTTP client generated by oapi-codegen is read the way a gRPC client is:
15
- // the tree already says which route each method calls. `New<Op>Request` builds
16
- // the request, and its body names the path and the verb; the document the
17
- // client was generated from is vendored beside it, and says which operation
18
- // answers on that route and which interface it belongs to. Put together, a
19
- // call on the client is `auth.v1.Sessions/login` - the same id the OpenAPI
20
- // extractor gives the method on the other side, because both spell it with
21
- // the same package.
22
-
23
- // The four names a generated client is held by in a use case or an adapter.
24
- var httpClientTypes = []string{"Client", "ClientInterface", "ClientWithResponses", "ClientWithResponsesInterface"}
25
-
26
- // isHTTPClient says whether a package is one oapi-codegen generated a client
27
- // into: a ClientInterface and at least one request builder.
28
- func isHTTPClient(p *pkg) bool {
29
- var iface, builder bool
30
- for _, file := range p.files {
31
- for _, decl := range file.Decls {
32
- switch d := decl.(type) {
33
- case *ast.GenDecl:
34
- for _, spec := range d.Specs {
35
- if ts, ok := spec.(*ast.TypeSpec); ok && ts.Name.Name == "ClientInterface" {
36
- if _, ok := ts.Type.(*ast.InterfaceType); ok {
37
- iface = true
38
- }
39
- }
40
- }
41
- case *ast.FuncDecl:
42
- if d.Recv == nil && requestBuilderOp(d.Name.Name) != "" {
43
- builder = true
44
- }
45
- }
46
- }
47
- }
48
-
49
- return iface && builder
50
- }
51
-
52
- // requestBuilderOp reads the operation's Go name off a request builder:
53
- // NewLoginRequest and NewLoginRequestWithBody are both Login.
54
- func requestBuilderOp(name string) string {
55
- rest, ok := strings.CutPrefix(name, "New")
56
- if !ok {
57
- return ""
58
- }
59
- if op, ok := strings.CutSuffix(rest, "RequestWithBody"); ok {
60
- return op
61
- }
62
- if op, ok := strings.CutSuffix(rest, "Request"); ok {
63
- return op
64
- }
65
-
66
- return ""
67
- }
68
-
69
- // readHTTPClient reads the routes the generated client calls and names them
70
- // by the vendored document. The problem, when there is one, is a sentence
71
- // for the reader of the diagnostics: no document beside the client, or a
72
- // route the document does not declare.
73
- func readHTTPClient(p *pkg) (map[string]client, string) {
74
- spec, source, problem := specBeside(p)
75
- if problem != "" {
76
- return nil, problem
77
- }
78
-
79
- c := client{pkg: spec.API, methods: map[string]string{}, source: source, external: openapi.ExternalID(spec.Title)}
80
- var unknown []string
81
- for _, file := range p.files {
82
- for _, decl := range file.Decls {
83
- fn, ok := decl.(*ast.FuncDecl)
84
- if !ok || fn.Recv != nil {
85
- continue
86
- }
87
- op := requestBuilderOp(fn.Name.Name)
88
- if op == "" {
89
- continue
90
- }
91
- verb, path := routeOf(fn)
92
- if verb == "" || path == "" {
93
- continue
94
- }
95
- found, ok := spec.Find(verb, path)
96
- if !ok {
97
- unknown = append(unknown, verb+" "+path)
98
-
99
- continue
100
- }
101
- id := found.CallID(spec.API)
102
- // Every way the client offers the operation, to the same call.
103
- for _, variant := range []string{op, op + "WithBody", op + "WithResponse", op + "WithBodyWithResponse"} {
104
- c.methods[variant] = id
105
- }
106
- }
107
- }
108
-
109
- out := map[string]client{}
110
- for _, name := range httpClientTypes {
111
- out[name] = c
112
- }
113
- if len(unknown) > 0 {
114
- return out, "the client in " + p.dir + " calls " + strings.Join(unknown, ", ") + ", which the document beside it does not declare; those calls are left out"
115
- }
116
-
117
- return out, ""
118
- }
119
-
120
- // specBeside finds the document the client was generated from, in the same
121
- // directory, and reads it.
122
- func specBeside(p *pkg) (*openapi.Spec, string, string) {
123
- for _, name := range []string{"openapi.yaml", "openapi.yml", "swagger.yaml", "swagger.yml"} {
124
- path := filepath.Join(filepath.FromSlash(p.dir), name)
125
- if _, err := os.Stat(path); err != nil {
126
- continue
127
- }
128
- spec, err := openapi.Read(path)
129
- if err != nil {
130
- return nil, "", err.Error()
131
- }
132
-
133
- return spec, filepath.ToSlash(path), ""
134
- }
135
-
136
- return nil, "", "the client in " + p.dir + " has no openapi document beside it, so its calls cannot be named; vendor the document the client was generated from"
137
- }
138
-
139
- // routeOf reads the verb and path out of a request builder's body:
140
- //
141
- // operationPath := fmt.Sprintf("/v1/users/%s", pathParam0)
142
- // req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil)
143
- func routeOf(fn *ast.FuncDecl) (verb, path string) {
144
- ast.Inspect(fn.Body, func(node ast.Node) bool {
145
- switch n := node.(type) {
146
- case *ast.AssignStmt:
147
- if len(n.Lhs) == 1 && len(n.Rhs) == 1 {
148
- if ident, ok := n.Lhs[0].(*ast.Ident); ok && ident.Name == "operationPath" {
149
- if call, ok := n.Rhs[0].(*ast.CallExpr); ok && len(call.Args) > 0 {
150
- if lit, ok := call.Args[0].(*ast.BasicLit); ok && lit.Kind == token.STRING {
151
- path, _ = strconv.Unquote(lit.Value)
152
- }
153
- }
154
- }
155
- }
156
- case *ast.CallExpr:
157
- if sel, ok := n.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "NewRequest" && len(n.Args) > 0 {
158
- switch arg := n.Args[0].(type) {
159
- case *ast.SelectorExpr:
160
- // http.MethodPost
161
- verb = strings.ToUpper(strings.TrimPrefix(arg.Sel.Name, "Method"))
162
- case *ast.BasicLit:
163
- if s, err := strconv.Unquote(arg.Value); err == nil {
164
- verb = strings.ToUpper(s)
165
- }
166
- }
167
- }
168
- }
169
-
170
- return true
171
- })
172
-
173
- return verb, path
174
- }
@@ -1,296 +0,0 @@
1
- package main
2
-
3
- import (
4
- "os"
5
- "path/filepath"
6
- "strings"
7
- "testing"
8
-
9
- "github.com/shortlink-org/portolan/catalog"
10
- "github.com/shortlink-org/portolan/plugin"
11
- )
12
-
13
- // The shape oapi-codegen v2 generates, cut to what the reader looks at: the
14
- // interface, and the request builders with their route.
15
- const generatedHTTPClient = `package gen
16
-
17
- import (
18
- "context"
19
- "fmt"
20
- "io"
21
- "net/http"
22
- )
23
-
24
- type ClientInterface interface {
25
- LoginWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
26
- Login(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
27
- GetUser(ctx context.Context, userId string, reqEditors ...RequestEditorFn) (*http.Response, error)
28
- }
29
-
30
- type ClientWithResponsesInterface interface {
31
- LoginWithResponse(ctx context.Context, body LoginJSONRequestBody, reqEditors ...RequestEditorFn) (*LoginResponse, error)
32
- GetUserWithResponse(ctx context.Context, userId string, reqEditors ...RequestEditorFn) (*GetUserResponse, error)
33
- }
34
-
35
- type ClientWithResponses struct{ ClientInterface }
36
-
37
- func NewLoginRequest(server string, body LoginJSONRequestBody) (*http.Request, error) {
38
- return NewLoginRequestWithBody(server, "application/json", nil)
39
- }
40
-
41
- func NewLoginRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
42
- operationPath := fmt.Sprintf("/v1/sessions")
43
- req, err := http.NewRequest(http.MethodPost, server+operationPath, body)
44
- return req, err
45
- }
46
-
47
- func NewGetUserRequest(server string, userId string) (*http.Request, error) {
48
- pathParam0 := userId
49
- operationPath := fmt.Sprintf("/v1/users/%s", pathParam0)
50
- req, err := http.NewRequest("GET", server+operationPath, nil)
51
- return req, err
52
- }
53
-
54
- func NewPingRequest(server string) (*http.Request, error) {
55
- operationPath := fmt.Sprintf("/v1/ping")
56
- req, err := http.NewRequest(http.MethodGet, server+operationPath, nil)
57
- return req, err
58
- }
59
- `
60
-
61
- const vendoredSpec = `openapi: 3.0.3
62
- info:
63
- title: auth
64
- version: 1.0.0
65
- paths:
66
- /v1/sessions:
67
- post:
68
- operationId: login
69
- tags: [sessions]
70
- /v1/users/{userId}:
71
- get:
72
- operationId: getUser
73
- tags: [users]
74
- `
75
-
76
- func writeTree(t *testing.T, root string, files map[string]string) {
77
- t.Helper()
78
- for rel, contents := range files {
79
- if err := os.MkdirAll(filepath.Dir(filepath.Join(root, rel)), 0o755); err != nil {
80
- t.Fatal(err)
81
- }
82
- if err := os.WriteFile(filepath.Join(root, rel), []byte(contents), 0o644); err != nil {
83
- t.Fatal(err)
84
- }
85
- }
86
- }
87
-
88
- // The route is read off the request builder and named by the document
89
- // beside it, and every way the client offers the operation is the same call.
90
- func TestAnHTTPClientIsNamedByTheDocumentBesideIt(t *testing.T) {
91
- root := t.TempDir()
92
- writeTree(t, root, map[string]string{
93
- "internal/infrastructure/auth/gen/client.gen.go": generatedHTTPClient,
94
- "internal/infrastructure/auth/gen/openapi.yaml": vendoredSpec,
95
- })
96
- pkg, err := parsePkg(root, "internal/infrastructure/auth/gen")
97
- if err != nil {
98
- t.Fatal(err)
99
- }
100
-
101
- clients, problem := readClients(pkg)
102
- if !strings.Contains(problem, "GET /v1/ping") {
103
- t.Errorf("a route the document does not declare should be reported: %q", problem)
104
- }
105
- c, ok := clients["ClientWithResponsesInterface"]
106
- if !ok || c.pkg != "auth.v1" {
107
- t.Fatalf("clients = %+v", clients)
108
- }
109
- for _, name := range httpClientTypes {
110
- if _, ok := clients[name]; !ok {
111
- t.Errorf("no client under %s", name)
112
- }
113
- }
114
- want := map[string]string{
115
- "Login": "auth.v1/login",
116
- "LoginWithBody": "auth.v1/login",
117
- "LoginWithResponse": "auth.v1/login",
118
- "GetUserWithResponse": "auth.v1/getUser",
119
- "GetUser": "auth.v1/getUser",
120
- "GetUserWithBodyWithResponse": "auth.v1/getUser",
121
- }
122
- for method, id := range want {
123
- if c.methods[method] != id {
124
- t.Errorf("%s = %q, want %q", method, c.methods[method], id)
125
- }
126
- }
127
- if _, named := c.methods["Ping"]; named {
128
- t.Error("a route the document does not declare was given a name")
129
- }
130
- if !strings.HasSuffix(c.source, "openapi.yaml") {
131
- t.Errorf("source = %q, want the document", c.source)
132
- }
133
- }
134
-
135
- // Without the document the client cannot be named, and the reader says so
136
- // rather than inventing an id from the route.
137
- func TestAnHTTPClientWithoutItsDocumentIsReported(t *testing.T) {
138
- root := t.TempDir()
139
- writeTree(t, root, map[string]string{
140
- "internal/infrastructure/auth/gen/client.gen.go": generatedHTTPClient,
141
- })
142
- pkg, err := parsePkg(root, "internal/infrastructure/auth/gen")
143
- if err != nil {
144
- t.Fatal(err)
145
- }
146
- clients, problem := readClients(pkg)
147
- if len(clients) != 0 || !strings.Contains(problem, "no openapi document beside it") {
148
- t.Errorf("clients = %v, problem = %q", clients, problem)
149
- }
150
- }
151
-
152
- // The whole chain over HTTP: a use case holds the generated client as a
153
- // port, and a call on it is an rpc to the service the manifest names for the
154
- // api, with the call on the service's consumes.
155
- func TestAUseCaseCallingAnHTTPClientIsAnRpcHop(t *testing.T) {
156
- root := t.TempDir()
157
- writeTree(t, root, map[string]string{
158
- "go.mod": "module github.com/example/gateway\n\ngo 1.27\n",
159
- "internal/infrastructure/auth/gen/client.gen.go": generatedHTTPClient,
160
- "internal/infrastructure/auth/gen/openapi.yaml": vendoredSpec,
161
- "internal/application/session/usecases/whoami/usecase.go": `package whoami
162
-
163
- import (
164
- "context"
165
-
166
- "github.com/example/gateway/internal/infrastructure/auth/gen"
167
- )
168
-
169
- type UseCase struct {
170
- auth gen.ClientWithResponsesInterface
171
- }
172
-
173
- func (uc *UseCase) Handle(ctx context.Context, in dto.Input) (dto.Output, error) {
174
- resp, err := uc.auth.GetUserWithResponse(ctx, in.UserID)
175
- if err != nil {
176
- return dto.Output{}, err
177
- }
178
- switch resp.StatusCode() {
179
- case 200:
180
- _, err := uc.auth.LoginWithResponse(ctx, gen.LoginJSONRequestBody{})
181
- return dto.Output{}, err
182
- case 401, 403:
183
- return dto.Output{}, ErrRefused
184
- }
185
- return dto.Output{}, nil
186
- }
187
- `,
188
- })
189
-
190
- b := &plugin.Builder{}
191
- r := newFlowReader(root, flowOptions{context: "edge", svcID: "edge.gateway", service: "gateway", peers: map[string]string{"auth.v1": "auth.auth"}}, b)
192
- d := newDraft()
193
- d.lane(r.serviceLane())
194
- r.walkUseCase(d, "session/whoami", 0)
195
-
196
- if got := dump(d.steps); got != "s1:getUser alt3{s2:login } " {
197
- t.Fatalf("nodes = %q", got)
198
- }
199
- first := d.steps[0].(*catalog.Step)
200
- if first.Kind != catalog.StepRPC || first.To != "auth.auth" || first.Ref != "auth.v1/getUser" || first.Status != catalog.StatusDeclared {
201
- t.Errorf("first = %+v", first)
202
- }
203
- alt := d.steps[1].(*catalog.Alt)
204
- titles := []string{}
205
- for _, br := range alt.Branches {
206
- titles = append(titles, br.Title)
207
- }
208
- if strings.Join(titles, "|") != "resp.StatusCode() is 200|resp.StatusCode() is 401, 403|otherwise" {
209
- t.Errorf("titles = %q", titles)
210
- }
211
- if !alt.Branches[0].Terminal || !alt.Branches[1].Terminal || alt.Branches[2].Terminal {
212
- t.Errorf("terminal marks = %v %v %v", alt.Branches[0].Terminal, alt.Branches[1].Terminal, alt.Branches[2].Terminal)
213
- }
214
- calls := r.consumes()
215
- if len(calls) != 2 || calls[0].ID != "auth.v1/getUser" || calls[1].ID != "auth.v1/login" || calls[0].Peer != "auth.auth" {
216
- t.Errorf("consumes = %+v", calls)
217
- }
218
- for _, diag := range b.Response().Warnings() {
219
- if strings.Contains(diag.Message, "/v1/ping") {
220
- continue
221
- }
222
- t.Errorf("unexpected diagnostic: %s", diag.Message)
223
- }
224
- }
225
-
226
- // A generated HTTP client whose api no peers line claims calls the system its
227
- // vendored document is titled after: a service that vendors a document and
228
- // generates a client from it is calling whatever that document describes, and
229
- // nothing in this tree implements it. The manifest may still name the system
230
- // itself, under externals, and then that name wins.
231
- func TestAnHTTPClientNobodyClaimsCallsTheSystemItsDocumentIsTitledAfter(t *testing.T) {
232
- partnerSpec := strings.Replace(vendoredSpec, "title: auth", "title: Partner Booking API", 1)
233
- useCase := `package whoami
234
-
235
- import (
236
- "context"
237
-
238
- "github.com/example/gateway/internal/infrastructure/partner/gen"
239
- )
240
-
241
- type UseCase struct {
242
- partner gen.ClientWithResponsesInterface
243
- }
244
-
245
- func (uc *UseCase) Handle(ctx context.Context, in dto.Input) (dto.Output, error) {
246
- _, err := uc.partner.GetUserWithResponse(ctx, in.UserID)
247
- return dto.Output{}, err
248
- }
249
- `
250
- tree := map[string]string{
251
- "go.mod": "module github.com/example/gateway\n\ngo 1.27\n",
252
- "internal/infrastructure/partner/gen/client.gen.go": generatedHTTPClient,
253
- "internal/infrastructure/partner/gen/openapi.yaml": partnerSpec,
254
- "internal/application/session/usecases/whoami/usecase.go": useCase,
255
- }
256
-
257
- read := func(opts flowOptions) (*catalog.Step, []catalog.RpcCall, *plugin.Builder) {
258
- t.Helper()
259
- root := t.TempDir()
260
- writeTree(t, root, tree)
261
- b := &plugin.Builder{}
262
- r := newFlowReader(root, opts, b)
263
- d := newDraft()
264
- d.lane(r.serviceLane())
265
- r.walkUseCase(d, "session/whoami", 0)
266
- if len(d.steps) != 1 {
267
- t.Fatalf("nodes = %q", dump(d.steps))
268
- }
269
- return d.steps[0].(*catalog.Step), r.consumes(), b
270
- }
271
-
272
- step, calls, b := read(flowOptions{context: "edge", svcID: "edge.gateway", service: "gateway"})
273
- if step.Kind != catalog.StepRPC || step.To != "partner-booking" || step.Ref != "partner-booking-api.v1/getUser" || step.Status != catalog.StatusDeclared {
274
- t.Errorf("rpc step = %+v", step)
275
- }
276
- if len(calls) != 1 || calls[0].Peer != "partner-booking" || calls[0].Status != catalog.StatusDeclared {
277
- t.Errorf("consumes = %+v", calls)
278
- }
279
- var said bool
280
- for _, warning := range b.Warnings {
281
- said = said || strings.Contains(warning.Message, "read as calling partner-booking")
282
- }
283
- if !said {
284
- t.Errorf("nothing said about the reading; warnings = %+v", b.Warnings)
285
- }
286
-
287
- step, calls, _ = read(flowOptions{context: "edge", svcID: "edge.gateway", service: "gateway", externals: map[string]string{"partner-booking-api.v1": "partner"}})
288
- if step.To != "partner" || calls[0].Peer != "partner" {
289
- t.Errorf("with externals: step to %q, consumes %+v", step.To, calls)
290
- }
291
-
292
- step, calls, _ = read(flowOptions{context: "edge", svcID: "edge.gateway", service: "gateway", peers: map[string]string{"partner-booking-api.v1": "shop.partner"}})
293
- if step.To != "shop.partner" || calls[0].Peer != "shop.partner" {
294
- t.Errorf("with peers: step to %q, consumes %+v", step.To, calls)
295
- }
296
- }
@@ -1,92 +0,0 @@
1
- package main
2
-
3
- import "strings"
4
-
5
- // The id rules, in one place. Everything downstream - a table saying which
6
- // aggregate it persists, a flow step naming an event, a link on a page - is a
7
- // string built by one of these, so they are worth having exactly once.
8
-
9
- func serviceID(context, service string) string { return context + "." + service }
10
-
11
- func aggregateID(service, aggregate string) string { return service + "." + aggregate }
12
-
13
- func blockID(aggregate, block string) string { return aggregate + "." + block }
14
-
15
- func eventID(aggregate, name string) string { return aggregate + "." + name }
16
-
17
- // slug turns a Go identifier into the kebab-case form ids and urls use:
18
- // PriceList becomes price-list, Address becomes address, ID stays id.
19
- func slug(name string) string {
20
- var b strings.Builder
21
-
22
- runes := []rune(name)
23
- for i, r := range runes {
24
- upper := r >= 'A' && r <= 'Z'
25
- if upper && i > 0 {
26
- // A dash goes in at the start of a word, which is either a lower
27
- // letter followed by an upper one (orderLine) or the end of an
28
- // acronym followed by a word (HTTPServer).
29
- prevLower := runes[i-1] >= 'a' && runes[i-1] <= 'z'
30
- nextLower := i+1 < len(runes) && runes[i+1] >= 'a' && runes[i+1] <= 'z'
31
- if prevLower || nextLower {
32
- b.WriteRune('-')
33
- }
34
- }
35
- if upper {
36
- r = r - 'A' + 'a'
37
- }
38
- if r == '_' || r == '.' {
39
- r = '-'
40
- }
41
- b.WriteRune(r)
42
- }
43
-
44
- // A separator in the source and a word boundary at the same spot would
45
- // otherwise give email.Address the slug "email--address".
46
- return collapseDashes(b.String())
47
- }
48
-
49
- func collapseDashes(s string) string {
50
- for strings.Contains(s, "--") {
51
- s = strings.ReplaceAll(s, "--", "-")
52
- }
53
-
54
- return strings.Trim(s, "-")
55
- }
56
-
57
- // camel turns a directory name into the operation id a reader expects:
58
- // change_password becomes ChangePassword.
59
- func camel(name string) string {
60
- var b strings.Builder
61
- for _, word := range strings.FieldsFunc(name, func(r rune) bool { return r == '_' || r == '-' }) {
62
- runes := []rune(word)
63
- if runes[0] >= 'a' && runes[0] <= 'z' {
64
- runes[0] = runes[0] - 'a' + 'A'
65
- }
66
- b.WriteString(string(runes))
67
- }
68
-
69
- return b.String()
70
- }
71
-
72
- // title is the human name for a package: user becomes User, price_list becomes
73
- // Price List.
74
- // pascal is the name a struct would have for this package: price_list holds
75
- // PriceList. `title` puts the words apart for a page to read; a type name has
76
- // them joined.
77
- func pascal(name string) string {
78
- return strings.ReplaceAll(title(name), " ", "")
79
- }
80
-
81
- func title(name string) string {
82
- words := strings.FieldsFunc(name, func(r rune) bool { return r == '_' || r == '-' })
83
- for i, word := range words {
84
- runes := []rune(word)
85
- if runes[0] >= 'a' && runes[0] <= 'z' {
86
- runes[0] = runes[0] - 'a' + 'A'
87
- }
88
- words[i] = string(runes)
89
- }
90
-
91
- return strings.Join(words, " ")
92
- }