@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,307 +0,0 @@
1
- package main
2
-
3
- // The parser, on its own. Nothing here knows the catalog exists: a proto is
4
- // read into an AST, and whether that AST becomes a service or a message is
5
- // somebody else's question.
6
-
7
- import (
8
- "os"
9
- "strings"
10
- "testing"
11
- )
12
-
13
- func parseFile(t *testing.T, name string) (*File, []Note) {
14
- t.Helper()
15
-
16
- src, err := os.ReadFile("testdata/" + name)
17
- if err != nil {
18
- t.Fatal(err)
19
- }
20
- file, notes, err := Parse("testdata/"+name, string(src))
21
- if err != nil {
22
- t.Fatalf("parsing %s: %v", name, err)
23
- }
24
-
25
- return file, notes
26
- }
27
-
28
- func orders(t *testing.T) *File {
29
- t.Helper()
30
- file, notes := parseFile(t, "orders.proto")
31
- if len(notes) > 0 {
32
- t.Errorf("a well-formed proto3 file produced notes: %v", notes)
33
- }
34
-
35
- return file
36
- }
37
-
38
- func TestFileHeader(t *testing.T) {
39
- file := orders(t)
40
-
41
- if file.Syntax != "proto3" {
42
- t.Errorf("syntax: %q", file.Syntax)
43
- }
44
- if file.Package != "shop.v1" {
45
- t.Errorf("package: %q", file.Package)
46
- }
47
- if len(file.Imports) != 2 {
48
- t.Fatalf("imports: %v", file.Imports)
49
- }
50
- if file.Imports[0].Path != "shop/v1/money.proto" || file.Imports[0].Public {
51
- t.Errorf("plain import: %+v", file.Imports[0])
52
- }
53
- if !file.Imports[1].Public {
54
- t.Errorf("public import was not marked public: %+v", file.Imports[1])
55
- }
56
- if len(file.Options) != 1 || file.Options[0].Name != "go_package" {
57
- t.Errorf("options: %+v", file.Options)
58
- }
59
- }
60
-
61
- // All four streaming forms, because a bidi method drawn as unary is a lie about
62
- // how the two ends are coupled.
63
- func TestStreaming(t *testing.T) {
64
- svc := orders(t).Services[0]
65
-
66
- want := map[string][2]bool{
67
- "PlaceOrder": {false, false},
68
- "GetOrder": {false, false},
69
- "WatchOrder": {false, true},
70
- "ImportOrders": {true, false},
71
- "Sync": {true, true},
72
- "CancelOrder": {false, false},
73
- }
74
-
75
- if len(svc.Methods) != len(want) {
76
- t.Fatalf("methods: %d, want %d", len(svc.Methods), len(want))
77
- }
78
- for _, m := range svc.Methods {
79
- got := [2]bool{m.ClientStreaming, m.ServerStreaming}
80
- if got != want[m.Name] {
81
- t.Errorf("%s streams %v, want %v", m.Name, got, want[m.Name])
82
- }
83
- }
84
- }
85
-
86
- func TestMethodShapes(t *testing.T) {
87
- svc := orders(t).Services[0]
88
-
89
- place := svc.Methods[0]
90
- if place.Request != "PlaceOrderRequest" || place.Response != "PlaceOrderResponse" {
91
- t.Errorf("PlaceOrder: %q -> %q", place.Request, place.Response)
92
- }
93
- watch := svc.Methods[2]
94
- if watch.Response != "OrderEvent" {
95
- t.Errorf("a streamed response must name the message, not the stream: %q", watch.Response)
96
- }
97
- }
98
-
99
- // A method option body must not swallow the methods after it.
100
- func TestMethodOptionBody(t *testing.T) {
101
- svc := orders(t).Services[0]
102
-
103
- cancel := svc.Methods[5]
104
- if cancel.Name != "CancelOrder" {
105
- t.Fatalf("last method: %q", cancel.Name)
106
- }
107
- if !cancel.Deprecated {
108
- t.Error("a method with `option deprecated = true` is not marked deprecated")
109
- }
110
- }
111
-
112
- // Comments are the only documentation a proto carries, and they arrive three
113
- // different ways.
114
- func TestDocComments(t *testing.T) {
115
- file := orders(t)
116
- svc := file.Services[0]
117
-
118
- if !strings.HasPrefix(svc.Doc, "The order interface.") {
119
- t.Errorf("block comment above a service: %q", svc.Doc)
120
- }
121
- if svc.Methods[0].Doc != "Place a new order. The response carries the id the shop assigned." {
122
- t.Errorf("line comment above a method: %q", svc.Methods[0].Doc)
123
- }
124
- if svc.Methods[1].Doc != "Read one order back." {
125
- t.Errorf("trailing comment on a method: %q", svc.Methods[1].Doc)
126
- }
127
-
128
- place := messageNamed(t, file, "PlaceOrderRequest")
129
- if place.Fields[0].Doc != "Who is buying." {
130
- t.Errorf("comment above a field: %q", place.Fields[0].Doc)
131
- }
132
- // The field after a documented one must not inherit its comment.
133
- if place.Fields[1].Doc != "" {
134
- t.Errorf("an undocumented field borrowed a comment: %q", place.Fields[1].Doc)
135
- }
136
- }
137
-
138
- // Types are kept as written, because a reader comparing the page against the
139
- // file it came from should recognise what they are looking at.
140
- func TestFieldTypesAsWritten(t *testing.T) {
141
- place := messageNamed(t, orders(t), "PlaceOrderRequest")
142
-
143
- want := []struct{ name, typ, label string }{
144
- {"customer_id", "string", ""},
145
- {"items", "LineItem", "repeated"},
146
- {"coupon", "string", "optional"},
147
- {"metadata", "map<string, string>", ""},
148
- {"total", "Money", ""},
149
- }
150
-
151
- if len(place.Fields) != len(want) {
152
- t.Fatalf("fields: %d, want %d", len(place.Fields), len(want))
153
- }
154
- for i, w := range want {
155
- f := place.Fields[i]
156
- if f.Name != w.name || f.Type != w.typ || f.Label != w.label {
157
- t.Errorf("field %d: %s %s %s", i, f.Label, f.Type, f.Name)
158
- }
159
- if f.Number != i+1 {
160
- t.Errorf("%s is field %d, want %d", f.Name, f.Number, i+1)
161
- }
162
- }
163
- if len(place.Reserved) != 3 {
164
- t.Errorf("reserved: %v", place.Reserved)
165
- }
166
- }
167
-
168
- // A oneof constrains which field may be set; it is not a separate place a field
169
- // lives. So its members are in the message's field list AND named by the oneof.
170
- func TestOneofMembersAreFields(t *testing.T) {
171
- order := messageNamed(t, orders(t), "Order")
172
-
173
- if len(order.Oneofs) != 1 || order.Oneofs[0].Name != "settlement" {
174
- t.Fatalf("oneofs: %+v", order.Oneofs)
175
- }
176
- if len(order.Oneofs[0].Fields) != 2 {
177
- t.Fatalf("oneof members: %+v", order.Oneofs[0].Fields)
178
- }
179
-
180
- byName := map[string]*Field{}
181
- for _, f := range order.Fields {
182
- byName[f.Name] = f
183
- }
184
- if byName["paid"] == nil || byName["paid"].Oneof != "settlement" {
185
- t.Error("a oneof member is missing from the message's fields, or does not name its oneof")
186
- }
187
- if byName["ship_to"] == nil || byName["ship_to"].Oneof != "" {
188
- t.Error("a plain field claims to be in a oneof")
189
- }
190
- // The fields declared after the oneof must still be there.
191
- if byName["channel"] == nil {
192
- t.Error("a field declared after a oneof was swallowed")
193
- }
194
- }
195
-
196
- func TestNestedTypes(t *testing.T) {
197
- order := messageNamed(t, orders(t), "Order")
198
-
199
- if len(order.Messages) != 1 || order.Messages[0].Name != "Address" {
200
- t.Fatalf("nested messages: %+v", order.Messages)
201
- }
202
- if !order.Messages[0].Fields[1].Deprecated {
203
- t.Error("a field with [deprecated = true] is not marked deprecated")
204
- }
205
- if len(order.Enums) != 1 || order.Enums[0].Name != "Channel" {
206
- t.Fatalf("nested enums: %+v", order.Enums)
207
- }
208
- }
209
-
210
- func TestTopLevelEnum(t *testing.T) {
211
- file := orders(t)
212
-
213
- if len(file.Enums) != 1 {
214
- t.Fatalf("enums: %+v", file.Enums)
215
- }
216
- status := file.Enums[0]
217
- if status.Doc != "How far along an order is." {
218
- t.Errorf("enum doc: %q", status.Doc)
219
- }
220
- if len(status.Values) != 2 || status.Values[1].Number != 1 {
221
- t.Fatalf("values: %+v", status.Values)
222
- }
223
- if status.Values[1].Doc != "The shop has it." {
224
- t.Errorf("trailing comment on an enum value: %q", status.Values[1].Doc)
225
- }
226
- }
227
-
228
- // The tolerance rule: a construct the parser declines to model is NAMED and
229
- // read past, never dropped silently and never fatal. A vendored copy that has
230
- // been narrowed is the normal case, not the exception.
231
- func TestSkippedConstructsAreNamed(t *testing.T) {
232
- file, notes := parseFile(t, "awkward.proto")
233
-
234
- joined := strings.Join(messages(notes), "\n")
235
- for _, want := range []string{"extend", "group", "extensions"} {
236
- if !strings.Contains(joined, want) {
237
- t.Errorf("%q was skipped without saying so:\n%s", want, joined)
238
- }
239
- }
240
-
241
- // Everything around the skipped constructs still has to be read.
242
- if len(file.Services) != 1 || len(file.Services[0].Methods) != 1 {
243
- t.Errorf("a service after an unreadable construct was lost: %+v", file.Services)
244
- }
245
- old := messageNamed(t, file, "Old")
246
- byName := map[string]*Field{}
247
- for _, f := range old.Fields {
248
- byName[f.Name] = f
249
- }
250
- if byName["name"] == nil || byName["name"].Label != "required" {
251
- t.Error("a proto2 required field was not read")
252
- }
253
- if byName["count"] == nil || byName["count"].Default != "3" {
254
- t.Errorf("a default value was not read: %+v", byName["count"])
255
- }
256
- if byName["kept"] == nil {
257
- t.Error("the field after a group was swallowed with it")
258
- }
259
- }
260
-
261
- // Past a truncated file the parser is not reading proto any more, it is
262
- // guessing - so it says so rather than returning half a file.
263
- func TestTruncatedFile(t *testing.T) {
264
- _, _, err := Parse("broken.proto", "message Foo {\n string a = 1;\n // and then\n")
265
- if err == nil {
266
- t.Skip("an unterminated block is recoverable; only an unterminated token is not")
267
- }
268
- }
269
-
270
- func TestUnterminatedStringIsAnError(t *testing.T) {
271
- _, _, err := Parse("broken.proto", "option go_package = \"never closed;\n")
272
- if err == nil {
273
- t.Fatal("an unterminated string parsed cleanly")
274
- }
275
- if !strings.Contains(err.Error(), "broken.proto") {
276
- t.Errorf("the error does not name the file: %v", err)
277
- }
278
- }
279
-
280
- func TestUnterminatedBlockCommentIsAnError(t *testing.T) {
281
- _, _, err := Parse("broken.proto", "/* never closed\nmessage Foo {}\n")
282
- if err == nil {
283
- t.Fatal("an unterminated block comment parsed cleanly")
284
- }
285
- }
286
-
287
- func messageNamed(t *testing.T, file *File, name string) *Message {
288
- t.Helper()
289
-
290
- for _, m := range file.Messages {
291
- if m.Name == name {
292
- return m
293
- }
294
- }
295
- t.Fatalf("no message %q", name)
296
-
297
- return nil
298
- }
299
-
300
- func messages(notes []Note) []string {
301
- out := make([]string, 0, len(notes))
302
- for _, n := range notes {
303
- out = append(out, n.Message)
304
- }
305
-
306
- return out
307
- }
@@ -1,236 +0,0 @@
1
- package main
2
-
3
- // What a service ANSWERS, read from the protos it publishes.
4
- //
5
- // One rpc service becomes one catalog interface; one rpc method becomes one
6
- // catalog method, carrying the two shapes it moves and how it streams. The
7
- // messages listed alongside are the ones REACHABLE from those methods, walked
8
- // transitively - the same rule extract-openapi applies to schemas, for the same
9
- // reason its comment gives: a reader who has to follow three links to find out
10
- // what comes back has been given a worse document.
11
-
12
- import (
13
- "path/filepath"
14
- "sort"
15
- "strconv"
16
-
17
- "github.com/shortlink-org/portolan/catalog"
18
- "github.com/shortlink-org/portolan/plugin"
19
- )
20
-
21
- // shared collects the messages promoted to catalog.defs, by their bare name.
22
- type shared struct {
23
- defs map[string]catalog.TypeDef
24
- // from records which fully-qualified type each def key was taken from, so a
25
- // second message with the same short name can be spotted rather than
26
- // silently overwriting the first.
27
- from map[string]string
28
- on bool
29
- }
30
-
31
- func newShared(on bool) *shared {
32
- return &shared{defs: map[string]catalog.TypeDef{}, from: map[string]string{}, on: on}
33
- }
34
-
35
- // interfaces turns every rpc service in the owned files into a catalog entry.
36
- func interfaces(ix *Index, files []*File, moduleID string, sh *shared, b *plugin.Builder) []catalog.RpcService {
37
- var out []catalog.RpcService
38
-
39
- for _, file := range files {
40
- for _, svc := range file.Services {
41
- id := interfaceID(file.Package, svc.Name)
42
-
43
- provided := catalog.RpcService{
44
- ID: id,
45
- Methods: make([]catalog.RpcMethod, 0, len(svc.Methods)),
46
- Source: sourceRef(file.Path, svc.Line),
47
- Module: moduleID,
48
- }
49
-
50
- // The messages this interface moves, in the order they are first
51
- // reached: the request and response of each method, then whatever
52
- // those refer to.
53
- var queue []string
54
- seen := map[string]bool{}
55
-
56
- for _, m := range svc.Methods {
57
- method := catalog.RpcMethod{
58
- Name: m.Name,
59
- Doc: m.Doc,
60
- Deprecated: m.Deprecated,
61
- Streaming: streamingOf(m),
62
- }
63
-
64
- method.Request, method.RequestRef = shapeRef(ix, file, m.Request, sh, &queue, seen, id, b)
65
- method.Response, method.ResponseRef = shapeRef(ix, file, m.Response, sh, &queue, seen, id, b)
66
-
67
- provided.Methods = append(provided.Methods, method)
68
- }
69
-
70
- provided.Messages, provided.Enums = messagesFrom(ix, queue, seen, sh, id, b)
71
- out = append(out, provided)
72
- }
73
- }
74
-
75
- // Sorted, because the fragment is committed and compared by gen:check and
76
- // the file walk that produced these is only as ordered as the filesystem.
77
- sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
78
-
79
- return out
80
- }
81
-
82
- // streamingOf maps a method's two stream flags onto the catalog's one value.
83
- func streamingOf(m *Method) catalog.Streaming {
84
- switch {
85
- case m.ClientStreaming && m.ServerStreaming:
86
- return catalog.StreamingBidi
87
- case m.ClientStreaming:
88
- return catalog.StreamingClient
89
- case m.ServerStreaming:
90
- return catalog.StreamingServer
91
- default:
92
- // Unary. Empty rather than a fourth constant, because unary is the case
93
- // not worth writing down and the catalog says so by leaving it out.
94
- return ""
95
- }
96
- }
97
-
98
- // shapeRef names a method's request or response and queues it for listing.
99
- //
100
- // It returns the name as a reader says it and the defs key, when the type is
101
- // one the author shared through an import.
102
- func shapeRef(ix *Index, from *File, name string, sh *shared, queue *[]string, seen map[string]bool, where string, b *plugin.Builder) (string, string) {
103
- if name == "" {
104
- return "", ""
105
- }
106
-
107
- fqn, ok := ix.Resolve(from.Package, name)
108
- if !ok {
109
- // Not a failure. A narrowed vendored copy imports files nobody
110
- // vendored beside it, which is the whole reason this reader is tolerant.
111
- b.Warn(where, name+" is not declared in the protos read here; it is named but not described")
112
-
113
- return shortName(name), ""
114
- }
115
-
116
- if !seen[fqn] {
117
- seen[fqn] = true
118
- *queue = append(*queue, fqn)
119
- }
120
-
121
- // Shared by the same rule as a field's type: the author put it in another
122
- // file, which is their own signal that it is not local to this interface.
123
- ref := ""
124
- if sh.on && ix.FileOf(fqn) != from {
125
- ref = sh.promote(ix, fqn, where, b)
126
- }
127
-
128
- return shortName(fqn), ref
129
- }
130
-
131
- // messagesFrom walks the queue, describing each message and adding whatever its
132
- // fields refer to, until nothing new is reached. An enum a field names is
133
- // reached the same way and listed beside the messages: a consumer decoding
134
- // the field sees a number, and the enum is what the number means.
135
- func messagesFrom(ix *Index, queue []string, seen map[string]bool, sh *shared, where string, b *plugin.Builder) ([]catalog.RpcMessage, []catalog.RpcEnum) {
136
- var out []catalog.RpcMessage
137
- var enums []catalog.RpcEnum
138
-
139
- for i := 0; i < len(queue); i++ {
140
- fqn := queue[i]
141
- msg := ix.Message(fqn)
142
- if msg == nil {
143
- if e := ix.enums[fqn]; e != nil {
144
- enums = append(enums, enumOf(fqn, e))
145
- }
146
- // Otherwise something only an import declares: not a message,
147
- // not a set, and not for this list.
148
- continue
149
- }
150
-
151
- fields := make([]catalog.Field, 0, len(msg.Fields))
152
- for _, f := range msg.Fields {
153
- ref := ix.resolveType(fqn, f)
154
- if ref.Unresolved != "" {
155
- b.Warn(where, ref.Unresolved+" is not declared in the protos read here; "+
156
- shortName(fqn)+"."+f.Name+" is listed with the type as written")
157
- }
158
-
159
- field := catalog.Field{Name: f.Name, Type: ref.Written, Doc: f.Doc}
160
-
161
- if ref.FQN != "" {
162
- if !seen[ref.FQN] {
163
- seen[ref.FQN] = true
164
- queue = append(queue, ref.FQN)
165
- }
166
- // A type is SHARED when the author put it in another file - an
167
- // import is their own signal that it is not local to this
168
- // message. No reference counting, no heuristic.
169
- if sh.on && ix.FileOf(ref.FQN) != ix.FileOf(fqn) {
170
- field.Ref = sh.promote(ix, ref.FQN, where, b)
171
- }
172
- }
173
-
174
- fields = append(fields, field)
175
- }
176
-
177
- out = append(out, catalog.RpcMessage{Name: shortName(fqn), Fields: fields})
178
- }
179
-
180
- return out, enums
181
- }
182
-
183
- // enumOf is an enum as the catalog carries it: the values in declaration
184
- // order with the numbers the wire uses. Named by its bare name, the way a
185
- // nested message is: a reader says `Channel`, and the file says where.
186
- func enumOf(fqn string, e *Enum) catalog.RpcEnum {
187
- values := make([]catalog.RpcEnumValue, 0, len(e.Values))
188
- for _, v := range e.Values {
189
- values = append(values, catalog.RpcEnumValue{Name: v.Name, Number: v.Number, Doc: v.Doc})
190
- }
191
-
192
- return catalog.RpcEnum{Name: shortName(fqn), Doc: e.Doc, Values: values}
193
- }
194
-
195
- // promote records a message as a shared type and returns its defs key.
196
- //
197
- // The key is the message's BARE name - `Money`, not `shop.v1.Money`. That is
198
- // what src/merge.ts argues for at length: bare keys are deliberate, so that two
199
- // sources meaning different things by `Money` produce a visible conflict
200
- // instead of two quietly coexisting entries. Fully-qualified keys would hide
201
- // exactly the problem the merge exists to expose.
202
- func (sh *shared) promote(ix *Index, fqn, where string, b *plugin.Builder) string {
203
- key := shortName(fqn)
204
-
205
- if previous, ok := sh.from[key]; ok {
206
- if previous != fqn {
207
- b.Warn(where, "two different types are both called "+key+" ("+previous+" and "+fqn+
208
- "); the first one read is the shared type")
209
- }
210
-
211
- return key
212
- }
213
-
214
- msg := ix.Message(fqn)
215
- if msg == nil {
216
- return ""
217
- }
218
-
219
- fields := make([]catalog.Field, 0, len(msg.Fields))
220
- for _, f := range msg.Fields {
221
- ref := ix.resolveType(fqn, f)
222
- fields = append(fields, catalog.Field{Name: f.Name, Type: ref.Written, Doc: f.Doc})
223
- }
224
-
225
- sh.from[key] = fqn
226
- sh.defs[key] = catalog.TypeDef{Fields: fields}
227
-
228
- return key
229
- }
230
-
231
- // sourceRef is where a declaration lives, as a reader would type it:
232
- // `proto/shop/v1/orders.proto:12`. Already the format `RpcService.source`
233
- // documents.
234
- func sourceRef(path string, line int) string {
235
- return filepath.ToSlash(path) + ":" + strconv.Itoa(line)
236
- }