@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,734 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "regexp"
6
- "sort"
7
- "strconv"
8
- "strings"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/plugin"
12
- )
13
-
14
- const laneClient = "client"
15
- const laneBus = "bus"
16
-
17
- // hop is one span read as a message between two lanes.
18
- type hop struct {
19
- from, to catalog.Participant
20
- kind catalog.StepKind
21
- ref string
22
- label string
23
- status catalog.Status
24
- entry bool // somebody calling in: the opening of an endpoint flow
25
- operation string // for an entry, the operation the route answers on
26
- consume bool
27
- eventID string
28
- wire string // for a publish, the event.name it carried
29
- call *catalog.RpcCall
30
- file string
31
- }
32
-
33
- // key is what makes two hops the same hop, for a declared step to match on.
34
- func (h hop) key() string {
35
- switch {
36
- case h.entry:
37
- return "entry|" + h.to.ID + "|" + h.operation
38
- case h.kind == catalog.StepEvent && h.consume:
39
- return "con|" + h.to.ID + "|" + h.eventID
40
- case h.kind == catalog.StepEvent:
41
- return "pub|" + h.from.ID + "|" + h.eventID
42
- case h.kind == catalog.StepRPC:
43
- return "rpc|" + h.from.ID + "|" + h.ref
44
- }
45
-
46
- return "call|" + h.from.ID + "|" + h.to.ID + "|" + h.label
47
- }
48
-
49
- type verifier struct {
50
- l *lookup
51
- b *plugin.Builder
52
- children map[string][]*span
53
- byID map[string]*span
54
- warned map[string]bool
55
-
56
- overlays map[string]*overlay // declared flow id -> its raised copy
57
- observed map[string]*observed
58
- consumers map[string]map[string]string // event id -> service id -> note
59
- calls map[string]catalog.RpcCall
60
- callers map[string]map[string]bool // call id -> service ids that made it
61
- }
62
-
63
- type overlay struct {
64
- flow *catalog.Flow
65
- svc *catalog.Service
66
- seen map[string]bool
67
- traces int
68
- file string
69
- }
70
-
71
- type observed struct {
72
- svc *catalog.Service
73
- root hop
74
- hops []hop
75
- traces int
76
- files map[string]bool
77
- }
78
-
79
- func verify(req plugin.Request, opts Options) (plugin.Response, error) {
80
- b := &plugin.Builder{}
81
- root := req.Input.Root
82
-
83
- spans, files, err := readTraces(root, opts.Traces)
84
- if err != nil {
85
- return plugin.Response{}, err
86
- }
87
- if len(files) == 0 {
88
- b.Warn(root, "no trace files matched "+strings.Join(opts.Traces, ", ")+"; nothing is verified")
89
- }
90
-
91
- v := &verifier{
92
- l: newLookup(&req.Catalog, opts),
93
- b: b,
94
- children: map[string][]*span{},
95
- byID: map[string]*span{},
96
- warned: map[string]bool{},
97
- overlays: map[string]*overlay{},
98
- observed: map[string]*observed{},
99
- consumers: map[string]map[string]string{},
100
- calls: map[string]catalog.RpcCall{},
101
- callers: map[string]map[string]bool{},
102
- }
103
- for i := range spans {
104
- s := &spans[i]
105
- v.byID[s.spanID] = s
106
- }
107
- var roots []*span
108
- for i := range spans {
109
- s := &spans[i]
110
- if s.parentID == "" || v.byID[s.parentID] == nil {
111
- roots = append(roots, s)
112
-
113
- continue
114
- }
115
- v.children[s.parentID] = append(v.children[s.parentID], s)
116
- }
117
- sort.Slice(roots, func(i, j int) bool {
118
- if roots[i].start != roots[j].start {
119
- return roots[i].start < roots[j].start
120
- }
121
-
122
- return roots[i].spanID < roots[j].spanID
123
- })
124
-
125
- for _, r := range roots {
126
- v.trace(r)
127
- }
128
-
129
- fragment := v.fragment(req.Input)
130
- encoded, err := json.MarshalIndent(fragment, "", " ")
131
- if err != nil {
132
- return plugin.Response{}, err
133
- }
134
- b.File(firstNonEmpty(opts.Out, "observed.json"), string(encoded)+"\n")
135
-
136
- return b.Response(), nil
137
- }
138
-
139
- // trace reads one trace from its root: the root's sequence is matched to a
140
- // declared flow or written down as observed, and every consumer inside it is
141
- // the opening of a flow of its own, matched the same way.
142
- func (v *verifier) trace(root *span) {
143
- hops, spans := v.sequence(root)
144
- if len(hops) == 0 {
145
- return
146
- }
147
-
148
- for _, h := range hops {
149
- if h.call != nil {
150
- if _, seen := v.calls[h.call.ID]; !seen {
151
- v.calls[h.call.ID] = *h.call
152
- }
153
- if v.callers[h.call.ID] == nil {
154
- v.callers[h.call.ID] = map[string]bool{}
155
- }
156
- v.callers[h.call.ID][h.from.ID] = true
157
- }
158
- if h.consume && h.eventID != "" {
159
- if v.consumers[h.eventID] == nil {
160
- v.consumers[h.eventID] = map[string]string{}
161
- }
162
- if _, seen := v.consumers[h.eventID][h.to.ID]; !seen {
163
- v.consumers[h.eventID][h.to.ID] = "Seen consuming it in " + h.file + "."
164
- }
165
- }
166
- }
167
-
168
- matched := v.match(hops[0], hops)
169
- for i := 1; i < len(hops); i++ {
170
- if hops[i].consume {
171
- sub, _ := v.sequence(spans[i])
172
- v.match(sub[0], sub)
173
- }
174
- }
175
- if !matched {
176
- v.observe(hops)
177
- }
178
- }
179
-
180
- // match finds the declared flow the sequence opens, and raises what the
181
- // sequence shows. False when no flow opens that way.
182
- func (v *verifier) match(opening hop, hops []hop) bool {
183
- var key string
184
- switch {
185
- case opening.entry:
186
- key = openingKey(catalog.StepRPC, opening.to.ID, opening.operation)
187
- case opening.consume && opening.eventID != "":
188
- key = openingKey(catalog.StepEvent, opening.to.ID, opening.eventID)
189
- default:
190
- return false
191
- }
192
- flow := v.l.openings[key]
193
- if flow == nil {
194
- return false
195
- }
196
-
197
- o := v.overlays[flow.ID]
198
- if o == nil {
199
- o = &overlay{flow: copyFlow(flow), svc: v.l.services[opening.to.ID], seen: map[string]bool{}, file: opening.file}
200
- v.overlays[flow.ID] = o
201
- }
202
- o.traces++
203
- for _, h := range hops {
204
- o.seen[h.key()] = true
205
- }
206
-
207
- return true
208
- }
209
-
210
- // observe writes a sequence nobody declared down as seen, once per shape.
211
- func (v *verifier) observe(hops []hop) {
212
- var parts []string
213
- for _, h := range hops {
214
- parts = append(parts, h.from.ID+">"+h.to.ID+":"+string(h.kind)+":"+h.label+":"+h.ref)
215
- }
216
- shape := strings.Join(parts, " ")
217
-
218
- o := v.observed[shape]
219
- if o == nil {
220
- o = &observed{svc: v.l.services[hops[0].to.ID], root: hops[0], hops: hops, files: map[string]bool{}}
221
- if o.svc == nil {
222
- o.svc = v.l.services[hops[0].from.ID]
223
- }
224
- v.observed[shape] = o
225
- }
226
- o.traces++
227
- o.files[hops[0].file] = true
228
- }
229
-
230
- // sequence reads a span and everything under it, in the order it ran, into
231
- // hops. The spans come back alongside so a consumer's subtree can be found.
232
- //
233
- // What a span is read as depends on what it sits under: a statement under a
234
- // database call is the same call, and a publish under a publish of the same
235
- // event is the same publish - an outbox writes the event and a relay puts it
236
- // on the bus, each with a producer span, and the event left the service once.
237
- func (v *verifier) sequence(s *span) ([]hop, []*span) {
238
- var hops []hop
239
- var spans []*span
240
- var walk func(s *span, parentDB bool, publishing string)
241
- walk = func(s *span, parentDB bool, publishing string) {
242
- h, isDB := v.hop(s, parentDB, publishing)
243
- if h != nil {
244
- hops = append(hops, *h)
245
- spans = append(spans, s)
246
- if h.kind == catalog.StepEvent && !h.consume {
247
- publishing = h.wire
248
- }
249
- }
250
- kids := v.children[s.spanID]
251
- sort.Slice(kids, func(i, j int) bool {
252
- if kids[i].start != kids[j].start {
253
- return kids[i].start < kids[j].start
254
- }
255
-
256
- return kids[i].spanID < kids[j].spanID
257
- })
258
- for _, kid := range kids {
259
- walk(kid, isDB, publishing)
260
- }
261
- }
262
- walk(s, false, "")
263
-
264
- return hops, spans
265
- }
266
-
267
- var tableOf = regexp.MustCompile(`(?i)\b(?:INTO|FROM|UPDATE|JOIN)\s+([A-Za-z_][A-Za-z0-9_]*)`)
268
-
269
- // hop reads one span. The second result says whether the span was a database
270
- // call, so that the statements nested under one - a prepare inside a query -
271
- // are not read as a second call. publishing is the wire name of the event a
272
- // producer span above this one is publishing, so that the relay's span under
273
- // the outbox's is not read as a second publish.
274
- func (v *verifier) hop(s *span, parentDB bool, publishing string) (*hop, bool) {
275
- svc := v.l.service(s.service)
276
- if svc == nil {
277
- v.warnOnce("service:"+s.service, s.service, "spans from service.name "+strconv.Quote(s.service)+" match no service in the catalog; name it under `services` to say which one it is")
278
-
279
- return nil, false
280
- }
281
- me := v.l.serviceLane(svc)
282
- a := s.attrs
283
- op := a["messaging.operation.type"]
284
- if op == "" {
285
- op = a["messaging.operation"]
286
- }
287
-
288
- switch {
289
- case s.kind == kindServer && (a["http.route"] != "" || a["url.path"] != ""):
290
- route := firstNonEmpty(a["http.route"], a["url.path"])
291
- method := firstNonEmpty(a["http.request.method"], a["http.method"])
292
- operation, known := v.l.operation(svc, method, route)
293
- label := operation
294
- if !known {
295
- label = method + " " + route
296
- v.warnOnce("route:"+svc.ID+method+route, svc.ID, "answers on "+method+" "+route+", which no interface it provides declares; the flow opens on the route rather than an operation")
297
- }
298
-
299
- return &hop{
300
- from: catalog.Participant{ID: laneClient, Kind: catalog.ParticipantActor}, to: me,
301
- kind: catalog.StepRPC, label: label, status: catalog.StatusVerified,
302
- entry: known, operation: operation, file: s.file,
303
- }, false
304
-
305
- case s.kind == kindServer && a["rpc.service"] != "":
306
- return &hop{
307
- from: catalog.Participant{ID: laneClient, Kind: catalog.ParticipantActor}, to: me,
308
- kind: catalog.StepRPC, label: a["rpc.method"], status: catalog.StatusVerified,
309
- entry: true, operation: a["rpc.method"], file: s.file,
310
- }, false
311
-
312
- case s.kind == kindClient && a["http.request.method"] != "":
313
- // A call over HTTP to another service. The route is the request's
314
- // path; the provider is whichever service declares an operation on
315
- // that verb and route, read the way the OpenAPI extractor records
316
- // them - so the call and the method share one id.
317
- method := a["http.request.method"]
318
- path := requestPath(a)
319
- if path == "" {
320
- return nil, false
321
- }
322
- h := &hop{from: me, kind: catalog.StepRPC, file: s.file}
323
- if peer, op, iface := v.l.httpProvider(method, path); peer != nil {
324
- id := iface + "/" + op
325
- h.to = v.l.serviceLane(peer)
326
- h.ref = id
327
- h.label = op
328
- h.status = catalog.StatusVerified
329
- h.call = &catalog.RpcCall{ID: id, Peer: peer.ID, Status: catalog.StatusVerified, Source: s.file}
330
- } else {
331
- host := firstNonEmpty(a["server.address"], a["net.peer.name"], "http")
332
- h.to = catalog.Participant{ID: strings.ReplaceAll(host, ".", "-"), Kind: catalog.ParticipantUnknown, Label: host}
333
- h.label = method + " " + path
334
- h.status = catalog.StatusUnresolved
335
- v.warnOnce("http:"+method+path, svc.ID, "calls "+method+" "+path+" on "+host+", which no service in the catalog answers on; the hop is unresolved")
336
- }
337
-
338
- return h, false
339
-
340
- case s.kind == kindClient && a["rpc.service"] != "":
341
- iface, method := a["rpc.service"], a["rpc.method"]
342
- id := iface + "/" + method
343
- h := &hop{from: me, kind: catalog.StepRPC, label: method, file: s.file}
344
- if v.l.rpcIDs[id] {
345
- h.ref = id
346
- }
347
- if peer := v.l.providers[iface]; peer != nil {
348
- h.to = v.l.serviceLane(peer)
349
- h.status = catalog.StatusVerified
350
- h.call = &catalog.RpcCall{ID: id, Peer: peer.ID, Status: catalog.StatusVerified, Source: s.file}
351
- } else {
352
- pkg := iface
353
- if i := strings.LastIndex(iface, "."); i >= 0 {
354
- pkg = iface[:i]
355
- }
356
- h.to = catalog.Participant{ID: strings.ReplaceAll(pkg, ".", "-"), Kind: catalog.ParticipantUnknown, Label: pkg}
357
- h.status = catalog.StatusUnresolved
358
- h.call = &catalog.RpcCall{ID: id, Peer: pkg, Status: catalog.StatusUnresolved, Source: s.file}
359
- }
360
-
361
- return h, false
362
-
363
- case a["db.system.name"] != "" || a["db.system"] != "" || a["db.operation.name"] != "":
364
- if parentDB {
365
- return nil, true
366
- }
367
- verb := strings.ToUpper(firstNonEmpty(a["db.operation.name"], a["db.operation"]))
368
- switch verb {
369
- case "", "BEGIN", "COMMIT", "ROLLBACK":
370
- return nil, verb != ""
371
- }
372
- label := verb
373
- if m := tableOf.FindStringSubmatch(s.name); m != nil {
374
- label = verb + " " + m[1]
375
- }
376
- to := me
377
- if lane, ok := v.l.storeLane(svc); ok {
378
- to = lane
379
- }
380
-
381
- return &hop{from: me, to: to, kind: catalog.StepCall, label: label, status: catalog.StatusVerified, file: s.file}, true
382
-
383
- case s.kind == kindProducer || op == "publish" || op == "create" || op == "send":
384
- wire := a["event.name"]
385
- if wire == "" {
386
- v.warnOnce("publish:"+svc.ID, svc.ID, "publishes without an event.name attribute on the span, so the trace cannot say which event; the hop is left out")
387
-
388
- return nil, false
389
- }
390
- id, ok := v.l.event(svc, wire)
391
- if ok {
392
- // Checked on every span of the publish: the inner one, the
393
- // relay's, is the one that names the bus the event actually
394
- // went over.
395
- v.checkChannel(svc, id, a["messaging.destination.name"])
396
- }
397
- if wire == publishing {
398
- return nil, false
399
- }
400
- h := &hop{from: me, to: catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker}, kind: catalog.StepEvent, label: lastSegment(wire), status: catalog.StatusVerified, eventID: id, wire: wire, file: s.file}
401
- if ok {
402
- h.ref = id
403
- } else {
404
- h.status = catalog.StatusUnresolved
405
- v.warnOnce("event:"+wire, svc.ID, "publishes "+strconv.Quote(wire)+", which matches no event of its own in the catalog; name it under `events`")
406
- }
407
-
408
- return h, false
409
-
410
- case s.kind == kindConsumer || op == "receive" || op == "process" || op == "deliver":
411
- wire := a["event.name"]
412
- if wire == "" {
413
- v.warnOnce("consume:"+svc.ID, svc.ID, "consumes without an event.name attribute on the span, so the trace cannot say which event; the hop is left out")
414
-
415
- return nil, false
416
- }
417
- id, ok := v.l.event(nil, wire)
418
- h := &hop{from: catalog.Participant{ID: laneBus, Kind: catalog.ParticipantBroker}, to: me, kind: catalog.StepEvent, label: lastSegment(wire), status: catalog.StatusVerified, consume: true, eventID: id, file: s.file}
419
- if ok {
420
- h.ref = id
421
- } else {
422
- h.status = catalog.StatusUnresolved
423
- h.eventID = ""
424
- v.warnOnce("event:"+wire, svc.ID, "consumes "+strconv.Quote(wire)+", which matches no event in the catalog; name it under `events`")
425
- }
426
-
427
- return h, false
428
- }
429
-
430
- return nil, false
431
- }
432
-
433
- func (v *verifier) warnOnce(key, ref, message string) {
434
- if v.warned[key] {
435
- return
436
- }
437
- v.warned[key] = true
438
- v.b.Warn(ref, message)
439
- }
440
-
441
- // requestPath is the path of a client request, off url.full or url.path.
442
- func requestPath(a map[string]string) string {
443
- if p := a["url.path"]; p != "" {
444
- return p
445
- }
446
- full := a["url.full"]
447
- if full == "" {
448
- full = a["http.url"]
449
- }
450
- if full == "" {
451
- return ""
452
- }
453
- if i := strings.Index(full, "://"); i >= 0 {
454
- full = full[i+3:]
455
- if j := strings.Index(full, "/"); j >= 0 {
456
- full = full[j:]
457
- } else {
458
- full = "/"
459
- }
460
- }
461
- if i := strings.IndexAny(full, "?#"); i >= 0 {
462
- full = full[:i]
463
- }
464
-
465
- return full
466
- }
467
-
468
- // fragment is what goes back: every declared flow a trace opened, with the
469
- // steps it showed raised; every sequence nobody declared; and the consumers
470
- // and calls the traces prove, hung on copies of the entities that own them.
471
- func (v *verifier) fragment(in plugin.Input) catalog.Catalog {
472
- out := catalog.Catalog{
473
- GeneratedAt: in.GeneratedAt,
474
- Commit: in.Commit,
475
- Contexts: []catalog.BoundedContext{},
476
- Defs: map[string]catalog.TypeDef{},
477
- Flows: []catalog.Flow{},
478
- Adrs: []catalog.Adr{},
479
- }
480
-
481
- ids := make([]string, 0, len(v.overlays))
482
- for id := range v.overlays {
483
- ids = append(ids, id)
484
- }
485
- sort.Strings(ids)
486
- for _, id := range ids {
487
- o := v.overlays[id]
488
- raise(o)
489
- out.Flows = append(out.Flows, *o.flow)
490
- }
491
-
492
- shapes := make([]string, 0, len(v.observed))
493
- for shape := range v.observed {
494
- shapes = append(shapes, shape)
495
- }
496
- sort.Strings(shapes)
497
- for _, shape := range shapes {
498
- out.Flows = append(out.Flows, v.observedFlow(v.observed[shape]))
499
- }
500
- sort.SliceStable(out.Flows, func(i, j int) bool { return out.Flows[i].Slug < out.Flows[j].Slug })
501
-
502
- // Edges, on the services that hold them.
503
- services := map[string]*catalog.Service{}
504
- touch := func(svc *catalog.Service) *catalog.Service {
505
- if got := services[svc.ID]; got != nil {
506
- return got
507
- }
508
- copied := &catalog.Service{ID: svc.ID, Slug: svc.Slug, Name: svc.Name, Provides: []catalog.RpcService{}, Consumes: []catalog.RpcCall{}, Aggregates: []catalog.Aggregate{}}
509
- services[svc.ID] = copied
510
-
511
- return copied
512
- }
513
-
514
- callIDs := make([]string, 0, len(v.calls))
515
- for id := range v.calls {
516
- callIDs = append(callIDs, id)
517
- }
518
- sort.Strings(callIDs)
519
- for _, id := range callIDs {
520
- callers := make([]string, 0, len(v.callers[id]))
521
- for svcID := range v.callers[id] {
522
- callers = append(callers, svcID)
523
- }
524
- sort.Strings(callers)
525
- for _, svcID := range callers {
526
- svc := touch(v.l.services[svcID])
527
- svc.Consumes = append(svc.Consumes, v.calls[id])
528
- }
529
- }
530
-
531
- eventIDs := make([]string, 0, len(v.consumers))
532
- for id := range v.consumers {
533
- eventIDs = append(eventIDs, id)
534
- }
535
- sort.Strings(eventIDs)
536
- for _, eventID := range eventIDs {
537
- owner := v.l.eventOwner[eventID]
538
- agg := v.l.aggregate[eventID]
539
- ev := v.l.events[eventID]
540
- if owner == nil || agg == nil || ev == nil {
541
- continue
542
- }
543
- svc := touch(owner)
544
- var target *catalog.Aggregate
545
- for i := range svc.Aggregates {
546
- if svc.Aggregates[i].ID == agg.ID {
547
- target = &svc.Aggregates[i]
548
- }
549
- }
550
- if target == nil {
551
- svc.Aggregates = append(svc.Aggregates, catalog.Aggregate{
552
- ID: agg.ID, Slug: agg.Slug, Name: agg.Name, Readme: agg.Readme, Root: agg.Root,
553
- Entities: []catalog.Block{}, ValueObjects: []catalog.Block{}, Operations: []catalog.Operation{}, Events: []catalog.Event{},
554
- })
555
- target = &svc.Aggregates[len(svc.Aggregates)-1]
556
- }
557
- consumers := []catalog.EventConsumer{}
558
- byService := v.consumers[eventID]
559
- names := make([]string, 0, len(byService))
560
- for name := range byService {
561
- names = append(names, name)
562
- }
563
- sort.Strings(names)
564
- for _, name := range names {
565
- consumers = append(consumers, catalog.EventConsumer{Service: name, Status: catalog.StatusVerified, Note: byService[name]})
566
- }
567
- target.Events = append(target.Events, catalog.Event{ID: ev.ID, Slug: ev.Slug, Name: ev.Name, Versions: ev.Versions, Consumers: consumers})
568
- }
569
-
570
- svcIDs := make([]string, 0, len(services))
571
- for id := range services {
572
- svcIDs = append(svcIDs, id)
573
- }
574
- sort.Strings(svcIDs)
575
- contexts := map[string]*catalog.BoundedContext{}
576
- var order []string
577
- for _, id := range svcIDs {
578
- ctxID := contextOf(id)
579
- ctx := contexts[ctxID]
580
- if ctx == nil {
581
- src := v.l.contexts[ctxID]
582
- ctx = &catalog.BoundedContext{ID: ctxID, Slug: ctxID, Services: []catalog.Service{}}
583
- if src != nil {
584
- ctx.Slug, ctx.Name = src.Slug, src.Name
585
- }
586
- contexts[ctxID] = ctx
587
- order = append(order, ctxID)
588
- }
589
- ctx.Services = append(ctx.Services, *services[id])
590
- }
591
- for _, id := range order {
592
- out.Contexts = append(out.Contexts, *contexts[id])
593
- }
594
-
595
- return out
596
- }
597
-
598
- // raise marks the declared steps the traces showed. Only `declared` moves:
599
- // `unresolved` means the far end is not in the catalog, and a trace does not
600
- // put it there.
601
- func raise(o *overlay) {
602
- first := firstStep(o.flow.Steps)
603
- note := "Seen running in " + o.file + " (" + plural(o.traces, "trace") + ")."
604
- walkSteps(o.flow.Steps, func(step *catalog.Step) {
605
- if step.Status != catalog.StatusDeclared {
606
- return
607
- }
608
- var shown bool
609
- switch step.Kind {
610
- case catalog.StepRPC:
611
- if step == first && step.Ref == "" {
612
- shown = o.seen["entry|"+step.To+"|"+step.Label]
613
- } else if step.Ref != "" {
614
- shown = o.seen["rpc|"+step.From+"|"+step.Ref]
615
- }
616
- case catalog.StepEvent:
617
- if step.Ref != "" {
618
- shown = o.seen["pub|"+step.From+"|"+step.Ref] || o.seen["con|"+step.To+"|"+step.Ref]
619
- }
620
- }
621
- if !shown {
622
- return
623
- }
624
- step.Status = catalog.StatusVerified
625
- if step.Note == "" {
626
- step.Note = note
627
- }
628
- })
629
- }
630
-
631
- // observedFlow writes a sequence nobody declared down as a flow of its own.
632
- func (v *verifier) observedFlow(o *observed) catalog.Flow {
633
- var lanes []catalog.Participant
634
- lane := func(p catalog.Participant) string {
635
- for _, existing := range lanes {
636
- if existing.ID == p.ID {
637
- return p.ID
638
- }
639
- }
640
- lanes = append(lanes, p)
641
-
642
- return p.ID
643
- }
644
-
645
- steps := catalog.FlowNodes{}
646
- for i, h := range o.hops {
647
- from := lane(h.from)
648
- to := lane(h.to)
649
- steps = append(steps, &catalog.Step{
650
- Type: "step", ID: "s" + strconv.Itoa(i+1), From: from, To: to,
651
- Kind: h.kind, Ref: h.ref, Label: h.label, Status: h.status,
652
- })
653
- }
654
-
655
- files := make([]string, 0, len(o.files))
656
- for f := range o.files {
657
- files = append(files, f)
658
- }
659
- sort.Strings(files)
660
-
661
- svc := o.svc
662
- slugged := "observed-" + svc.Slug + "-" + slugify(o.root.label)
663
-
664
- return catalog.Flow{
665
- ID: "flow." + slugged,
666
- Slug: slugged,
667
- Name: "Observed: " + o.root.label,
668
- Summary: "Read from " + plural(o.traces, "trace") + " in " + strings.Join(files, ", ") + ". No flow in the catalog opens this way, so the sequence is written down as it was seen.",
669
- Source: files[0],
670
- Owner: contextOf(svc.ID),
671
- Participants: lanes,
672
- Steps: steps,
673
- }
674
- }
675
-
676
- // copyFlow is a deep copy, through the wire form, so that raising never writes
677
- // into the catalog the plugin was handed.
678
- func copyFlow(flow *catalog.Flow) *catalog.Flow {
679
- raw, err := json.Marshal(flow)
680
- if err != nil {
681
- panic(err)
682
- }
683
- var out catalog.Flow
684
- if err := json.Unmarshal(raw, &out); err != nil {
685
- panic(err)
686
- }
687
-
688
- return &out
689
- }
690
-
691
- // checkChannel holds the channel a publish span names against the one the
692
- // catalog declares for the event. A trace is the one witness that the two
693
- // agree, and a publisher whose code and whose telemetry name different
694
- // channels has a bug in one of them; the hop is kept, because the event did
695
- // go out, and the disagreement is said once.
696
- func (v *verifier) checkChannel(svc *catalog.Service, eventID, destination string) {
697
- ev := v.l.events[eventID]
698
- if ev == nil || ev.Wire == nil || ev.Wire.Channel == "" || destination == "" || destination == ev.Wire.Channel {
699
- return
700
- }
701
- v.warnOnce("channel:"+eventID, svc.ID, "publishes "+strconv.Quote(ev.Wire.Name)+" on "+strconv.Quote(destination)+", but the catalog says it goes on "+strconv.Quote(ev.Wire.Channel))
702
- }
703
-
704
- func lastSegment(wire string) string {
705
- if i := strings.LastIndex(wire, "."); i >= 0 {
706
- return wire[i+1:]
707
- }
708
-
709
- return wire
710
- }
711
-
712
- var notSlug = regexp.MustCompile(`[^a-z0-9]+`)
713
-
714
- func slugify(s string) string {
715
- return strings.Trim(notSlug.ReplaceAllString(strings.ToLower(s), "-"), "-")
716
- }
717
-
718
- func plural(n int, noun string) string {
719
- if n == 1 {
720
- return "1 " + noun
721
- }
722
-
723
- return strconv.Itoa(n) + " " + noun + "s"
724
- }
725
-
726
- func firstNonEmpty(values ...string) string {
727
- for _, value := range values {
728
- if value != "" {
729
- return value
730
- }
731
- }
732
-
733
- return ""
734
- }