@shortlink-org/portolan 0.2.3 → 0.3.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 (184) hide show
  1. package/README.md +23 -0
  2. package/catalog/enum_test.go +46 -0
  3. package/catalog/evidence_test.go +35 -0
  4. package/catalog/model.go +1066 -0
  5. package/catalog/roundtrip_test.go +203 -0
  6. package/catalog/via_test.go +38 -0
  7. package/cli/init.test.mjs +6 -1
  8. package/cli/portolan.mjs +8 -0
  9. package/cli/portolan.test.mjs +49 -0
  10. package/go.mod +14 -0
  11. package/go.sum +20 -0
  12. package/internal/gocall/README.md +19 -0
  13. package/internal/gocall/analyze.go +189 -0
  14. package/internal/gocall/analyze_test.go +107 -0
  15. package/internal/gohttp/analyze.go +2562 -0
  16. package/internal/gohttp/destination.go +373 -0
  17. package/internal/gohttp/endpoints.go +1067 -0
  18. package/internal/gohttp/roots.go +320 -0
  19. package/internal/gohttp/typed.go +96 -0
  20. package/internal/goscan/constants.go +85 -0
  21. package/internal/goscan/goscan_test.go +227 -0
  22. package/internal/goscan/index.go +629 -0
  23. package/internal/goscan/index_test.go +66 -0
  24. package/internal/goscan/names.go +52 -0
  25. package/internal/goscan/parse_test.go +11 -0
  26. package/internal/goscan/source.go +37 -0
  27. package/internal/goscan/tree.go +284 -0
  28. package/internal/goscan/types.go +99 -0
  29. package/internal/wsdl/ids.go +127 -0
  30. package/internal/wsdl/ids_test.go +21 -0
  31. package/internal/wsdl/model.go +70 -0
  32. package/internal/wsdl/parse.go +949 -0
  33. package/internal/wsdl/parse_test.go +170 -0
  34. package/package.json +22 -10
  35. package/plugin/describe.go +118 -0
  36. package/plugin/describe_test.go +114 -0
  37. package/plugin/protocol.go +141 -0
  38. package/plugin/schematest/schematest.go +126 -0
  39. package/plugins/README.md +114 -46
  40. package/plugins/cmd/portolan-http-clients/main.go +19 -0
  41. package/plugins/extract-celery/extract.py +0 -2
  42. package/plugins/extract-celery/extract_test.py +1 -1
  43. package/plugins/extract-django/README.md +39 -17
  44. package/plugins/extract-django/domain.py +28 -17
  45. package/plugins/extract-django/extract.py +21 -7
  46. package/plugins/extract-django/extract_test.py +55 -2
  47. package/plugins/extract-django/lifecycle.py +2 -0
  48. package/plugins/extract-django/operations.py +1 -1
  49. package/plugins/extract-django/routing_test.py +109 -1
  50. package/plugins/extract-django/store.py +1 -1
  51. package/plugins/extract-django/transport.py +101 -55
  52. package/plugins/extract-django/verbs.py +241 -0
  53. package/plugins/extract-go/README.md +47 -0
  54. package/plugins/extract-http-clients/describe.go +19 -0
  55. package/plugins/extract-http-clients/describe_test.go +11 -0
  56. package/plugins/extract-http-clients/extract.go +740 -0
  57. package/plugins/extract-http-clients/extract_test.go +1561 -0
  58. package/plugins/extract-http-clients/main.go +41 -0
  59. package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
  60. package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
  61. package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
  62. package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
  63. package/plugins/extract-python-kafka/README.md +6 -0
  64. package/plugins/extract-python-kafka/extract.py +2 -4
  65. package/plugins/extract-python-kafka/extract_test.py +18 -2
  66. package/plugins/extract-ts/extract.test.ts +2 -2
  67. package/plugins/extract-ts/extract.ts +4 -5
  68. package/plugins/extract-ts/graphql.test.ts +1 -1
  69. package/plugins/openapi/ids.go +261 -0
  70. package/plugins/openapi/ids_test.go +98 -0
  71. package/plugins/portolan-go.wasm +0 -0
  72. package/plugins/pyplugin/catalog.py +12 -1
  73. package/plugins/pyplugin/kafka.py +74 -3
  74. package/plugins/pyplugin/protocol.py +1 -5
  75. package/portolan.json +3 -2
  76. package/schema/portolan.schema.json +34 -0
  77. package/scripts/README.md +18 -12
  78. package/scripts/catalog-sources.mjs +6 -0
  79. package/scripts/delivery-presets.mjs +21 -11
  80. package/scripts/diff.mjs +5 -1
  81. package/scripts/django-aggregates.test.mjs +58 -0
  82. package/scripts/gen-likec4.mjs +79 -21
  83. package/scripts/gen-likec4.test.mjs +25 -2
  84. package/scripts/gen.mjs +118 -115
  85. package/scripts/go-discovery.test.mjs +30 -0
  86. package/scripts/history.mjs +186 -3
  87. package/scripts/history.test.mjs +1 -1
  88. package/scripts/host-plugins/fetch-git.mjs +77 -21
  89. package/scripts/host-plugins/fetch-git.test.mjs +62 -8
  90. package/scripts/local-api.mjs +71 -4
  91. package/scripts/local-api.test.mjs +63 -4
  92. package/scripts/local-discovery.mjs +82 -9
  93. package/scripts/manifest.mjs +5 -3
  94. package/scripts/manifest.test.mjs +24 -0
  95. package/scripts/output-diff.mjs +94 -0
  96. package/scripts/output-diff.test.mjs +36 -0
  97. package/scripts/package-smoke.mjs +62 -4
  98. package/scripts/plugin-host.mjs +22 -2
  99. package/scripts/plugin-host.test.mjs +9 -0
  100. package/scripts/plugin-wasm-worker.mjs +4 -1
  101. package/scripts/provenance.mjs +72 -0
  102. package/scripts/provenance.test.mjs +149 -0
  103. package/scripts/run-builtin.mjs +39 -5
  104. package/scripts/schema.mjs +29 -0
  105. package/scripts/warning-policy.mjs +161 -0
  106. package/scripts/warning-policy.test.mjs +56 -0
  107. package/src/app/Breadcrumbs.test.ts +4 -0
  108. package/src/app/Breadcrumbs.tsx +1 -0
  109. package/src/app/Sidebar.tsx +3 -3
  110. package/src/catalog-docs.test.ts +64 -0
  111. package/src/catalog-docs.ts +35 -0
  112. package/src/catalog-error.test.ts +15 -0
  113. package/src/catalog-model.ts +70 -6
  114. package/src/catalog-stores.test.ts +17 -0
  115. package/src/catalog-validation.ts +52 -2
  116. package/src/catalog.test.ts +13 -2
  117. package/src/chat/Starter.tsx +5 -11
  118. package/src/chat/tools.test.ts +27 -0
  119. package/src/chat/tools.ts +5 -9
  120. package/src/components/CatalogStamp.tsx +10 -8
  121. package/src/components/ChannelRows.messagepack.test.tsx +28 -0
  122. package/src/components/ChannelRows.test.tsx +54 -0
  123. package/src/components/ChannelRows.tsx +57 -10
  124. package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
  125. package/src/components/HTTPDestinationEvidence.tsx +31 -0
  126. package/src/components/Integrations.tsx +1 -1
  127. package/src/components/LifecycleDiagram.tsx +28 -12
  128. package/src/components/MachineDocs.tsx +6 -5
  129. package/src/components/MethodRows.tsx +9 -2
  130. package/src/components/ProblemRow.tsx +4 -0
  131. package/src/components/RelationEvidence.test.tsx +14 -0
  132. package/src/components/RelationEvidence.tsx +53 -0
  133. package/src/components/WhatLinksHere.tsx +6 -4
  134. package/src/data.ts +25 -7
  135. package/src/enrich.test.ts +336 -6
  136. package/src/enrich.ts +206 -3
  137. package/src/flow/StepDetail.tsx +104 -54
  138. package/src/flow/answers.test.ts +18 -1
  139. package/src/flow/answers.ts +37 -8
  140. package/src/flow/evidence.test.ts +16 -0
  141. package/src/flow/evidence.ts +34 -0
  142. package/src/index.css +44 -0
  143. package/src/landing/DraggableReveal.tsx +3 -2
  144. package/src/landing/EvidencePipeline.tsx +105 -0
  145. package/src/landing/LandingPage.tsx +2 -59
  146. package/src/lib/backlinks.test.ts +16 -1
  147. package/src/lib/backlinks.ts +20 -0
  148. package/src/lib/catalog-diff.test.ts +18 -0
  149. package/src/lib/catalog-diff.ts +20 -2
  150. package/src/lib/derive.ts +1 -0
  151. package/src/lib/django-aggregates.d.mts +9 -0
  152. package/src/lib/django-aggregates.mjs +36 -0
  153. package/src/lib/django-aggregates.test.ts +29 -0
  154. package/src/lib/django-aggregates.ts +5 -0
  155. package/src/lib/kafka-ui.test.ts +87 -0
  156. package/src/lib/kafka-ui.ts +105 -0
  157. package/src/lib/local-api.ts +20 -2
  158. package/src/lib/setup-info.test.ts +17 -0
  159. package/src/lib/setup-info.ts +58 -0
  160. package/src/lib/warnings.test.ts +54 -0
  161. package/src/lib/warnings.ts +260 -0
  162. package/src/lib/wire-problems.test.ts +21 -0
  163. package/src/lib/wire-problems.ts +62 -1
  164. package/src/likec4/FlowView.tsx +2 -6
  165. package/src/likec4/flow-edges.test.ts +64 -1
  166. package/src/likec4/flow-edges.ts +43 -7
  167. package/src/likec4/view-index.ts +8 -2
  168. package/src/map/ContextMapGraph.tsx +76 -32
  169. package/src/merge.test.ts +23 -0
  170. package/src/merge.ts +33 -10
  171. package/src/pages/AggregatePage.tsx +8 -7
  172. package/src/pages/CatalogFailure.tsx +2 -2
  173. package/src/pages/ContextPage.tsx +6 -5
  174. package/src/pages/ServicePage.tsx +4 -3
  175. package/src/pages/Settings.tsx +200 -44
  176. package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
  177. package/src/pages/settings/IntegrationsSettings.tsx +117 -0
  178. package/src/routes.test.ts +2 -0
  179. package/src/routes.ts +2 -1
  180. package/src/selection/DetailPanel.tsx +61 -1
  181. package/src/virtual-provenance.d.ts +11 -0
  182. package/vite.config.ts +5 -0
  183. package/scripts/vendor-lock.mjs +0 -58
  184. package/scripts/vendor-lock.test.mjs +0 -69
@@ -0,0 +1,373 @@
1
+ package gohttp
2
+
3
+ import (
4
+ "go/ast"
5
+ "go/token"
6
+ "net/url"
7
+ "reflect"
8
+ "sort"
9
+ "strconv"
10
+ "strings"
11
+
12
+ "github.com/shortlink-org/portolan/catalog"
13
+ )
14
+
15
+ type destinationObject map[string]catalog.HTTPBaseURL
16
+
17
+ // Keep constructor settings on the owning adapter field, never on the shared
18
+ // HTTP client type. Conflicting or unreadable construction sites invalidate
19
+ // the field instead of borrowing another instance's destination.
20
+ func (s *scanner) indexDestinations() {
21
+ s.destinations = map[string]destinationObject{}
22
+ candidates := map[string][]destinationObject{}
23
+ keys := make([]string, 0, len(s.functions))
24
+ for key := range s.functions {
25
+ keys = append(keys, key)
26
+ }
27
+ sort.Strings(keys)
28
+ for _, key := range keys {
29
+ caller := s.functions[key]
30
+ ast.Inspect(caller.fn.Body, func(n ast.Node) bool {
31
+ call, ok := n.(*ast.CallExpr)
32
+ if !ok {
33
+ return true
34
+ }
35
+ target := s.functions[s.localTarget(caller, call.Fun)]
36
+ if target == nil {
37
+ return true
38
+ }
39
+ results := s.expressionResultTypes(caller, call, nil)
40
+ if len(results) == 0 || results[0].name == "" {
41
+ return true
42
+ }
43
+ object := s.destinationObject(caller, call, nil, map[string]bool{})
44
+ // A wrapper receives a configured client; a bare Client constructor must
45
+ // not establish a type-wide base URL shared by all its instances.
46
+ nested := destinationObject{}
47
+ for field, base := range object {
48
+ if strings.Contains(field, ".") {
49
+ nested[field] = base
50
+ }
51
+ }
52
+ owner := fieldTypeKey(results[0], "")
53
+ candidates[owner] = append(candidates[owner], nested)
54
+ return true
55
+ })
56
+ }
57
+ for owner, objects := range candidates {
58
+ if len(objects) == 0 {
59
+ continue
60
+ }
61
+ common := objects[0]
62
+ for field, base := range common {
63
+ for _, object := range objects[1:] {
64
+ other, ok := object[field]
65
+ if !ok || base.Value != other.Value || base.ConfigField != other.ConfigField || base.EnvironmentVariable != other.EnvironmentVariable || base.Kind != other.Kind {
66
+ delete(common, field)
67
+ break
68
+ }
69
+ }
70
+ }
71
+ if len(common) > 0 {
72
+ s.destinations[owner] = common
73
+ }
74
+ }
75
+ }
76
+
77
+ // Read returned struct fields and the narrow functional-option shape
78
+ // func(value string) Option { return func(c *Client) { c.baseURL = value } }.
79
+ func (s *scanner) destinationObject(owner *functionDecl, expr ast.Expr, bound map[string]destinationObject, seen map[string]bool) destinationObject {
80
+ if id, ok := expr.(*ast.Ident); ok {
81
+ return bound[id.Name]
82
+ }
83
+ if literal := compositeLiteral(expr); literal != nil {
84
+ result := destinationObject{}
85
+ for _, element := range literal.Elts {
86
+ pair, ok := element.(*ast.KeyValueExpr)
87
+ if !ok {
88
+ continue
89
+ }
90
+ for field, base := range s.destinationObject(owner, pair.Value, bound, seen) {
91
+ result[expression(pair.Key)+"."+field] = base
92
+ }
93
+ }
94
+ return result
95
+ }
96
+ call, ok := expr.(*ast.CallExpr)
97
+ if !ok {
98
+ return nil
99
+ }
100
+ key := s.localTarget(owner, call.Fun)
101
+ target := s.functions[key]
102
+ if target == nil || seen[key] {
103
+ return nil
104
+ }
105
+ seen[key] = true
106
+ defer delete(seen, key)
107
+ args := map[string]destinationObject{}
108
+ for i, param := range functionParams(target.fn) {
109
+ if i < len(call.Args) {
110
+ args[param] = s.destinationObject(owner, call.Args[i], bound, seen)
111
+ }
112
+ }
113
+ // Only apply options if the constructor actually invokes each option on
114
+ // the same local object it returns.
115
+ var returned string
116
+ for _, statement := range target.fn.Body.List {
117
+ if ret, ok := statement.(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
118
+ returned = expression(ret.Results[0])
119
+ }
120
+ }
121
+ optionsApplied := false
122
+ variadic := ""
123
+ if functionVariadic(target.fn) {
124
+ params := functionParams(target.fn)
125
+ variadic = params[len(params)-1]
126
+ }
127
+ ast.Inspect(target.fn.Body, func(n ast.Node) bool {
128
+ loop, ok := n.(*ast.RangeStmt)
129
+ if !ok || variadic == "" || expression(loop.X) != variadic {
130
+ return true
131
+ }
132
+ for _, stmt := range loop.Body.List {
133
+ statement, ok := stmt.(*ast.ExprStmt)
134
+ if !ok {
135
+ continue
136
+ }
137
+ invocation, ok := statement.X.(*ast.CallExpr)
138
+ if ok && expression(invocation.Fun) == expression(loop.Value) && len(invocation.Args) == 1 && expression(invocation.Args[0]) == returned {
139
+ optionsApplied = true
140
+ }
141
+ }
142
+ return false
143
+ })
144
+ for _, statement := range target.fn.Body.List {
145
+ if assign, ok := statement.(*ast.AssignStmt); ok {
146
+ for i, left := range assign.Lhs {
147
+ if i < len(assign.Rhs) {
148
+ args[expression(left)] = s.destinationObject(target, assign.Rhs[i], args, seen)
149
+ }
150
+ }
151
+ }
152
+ }
153
+ var result destinationObject
154
+ for _, statement := range target.fn.Body.List {
155
+ if ret, ok := statement.(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
156
+ result = s.destinationObject(target, ret.Results[0], args, seen)
157
+ }
158
+ }
159
+ if result == nil {
160
+ result = destinationObject{}
161
+ }
162
+ if optionsApplied && len(call.Args) >= len(functionParams(target.fn))-1 {
163
+ start := len(functionParams(target.fn)) - 1
164
+ for _, argument := range call.Args[start:] {
165
+ option, ok := argument.(*ast.CallExpr)
166
+ if !ok {
167
+ continue
168
+ }
169
+ setter := s.functions[s.localTarget(owner, option.Fun)]
170
+ if setter == nil {
171
+ continue
172
+ }
173
+ params := functionParams(setter.fn)
174
+ for _, statement := range setter.fn.Body.List {
175
+ ret, ok := statement.(*ast.ReturnStmt)
176
+ if !ok || len(ret.Results) != 1 {
177
+ continue
178
+ }
179
+ closure, ok := ret.Results[0].(*ast.FuncLit)
180
+ if !ok || len(closure.Type.Params.List) != 1 || len(closure.Type.Params.List[0].Names) != 1 {
181
+ continue
182
+ }
183
+ receiver := closure.Type.Params.List[0].Names[0].Name
184
+ for _, stmt := range closure.Body.List {
185
+ assign, ok := stmt.(*ast.AssignStmt)
186
+ if !ok || assign.Tok != token.ASSIGN || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
187
+ continue
188
+ }
189
+ field, ok := assign.Lhs[0].(*ast.SelectorExpr)
190
+ if !ok || expression(field.X) != receiver || !looksLikeURLName(field.Sel.Name) {
191
+ continue
192
+ }
193
+ for i, param := range params {
194
+ if expression(assign.Rhs[0]) == param && i < len(option.Args) {
195
+ base := s.destinationBase(owner, option.Args[i])
196
+ base.OptionSource = s.source(owner.file, option.Pos()).String()
197
+ result[field.Sel.Name] = base
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ return result
205
+ }
206
+
207
+ func (s *scanner) destinationBase(owner *functionDecl, expr ast.Expr) catalog.HTTPBaseURL {
208
+ base := catalog.HTTPBaseURL{Expression: expression(expr), Kind: "symbolic", Source: s.source(owner.file, expr.Pos()).String()}
209
+ if literal, ok := expr.(*ast.BasicLit); ok && literal.Kind == token.STRING {
210
+ base.Value, _ = strconv.Unquote(literal.Value)
211
+ base.Kind = "literal"
212
+ return base
213
+ }
214
+ selector, ok := expr.(*ast.SelectorExpr)
215
+ if !ok {
216
+ return base
217
+ }
218
+ base.ConfigField = s.symbolicValue(owner, expr, nil, map[string]bool{})
219
+ typ, ok := s.destinationExprType(owner, selector.X)
220
+ if !ok {
221
+ return base
222
+ }
223
+ for _, file := range s.files {
224
+ if file.dir != typ.dir {
225
+ continue
226
+ }
227
+ for _, decl := range file.node.Decls {
228
+ gen, ok := decl.(*ast.GenDecl)
229
+ if !ok {
230
+ continue
231
+ }
232
+ for _, spec := range gen.Specs {
233
+ named, ok := spec.(*ast.TypeSpec)
234
+ if !ok || named.Name.Name != typ.name {
235
+ continue
236
+ }
237
+ structure, ok := named.Type.(*ast.StructType)
238
+ if !ok {
239
+ continue
240
+ }
241
+ for _, field := range structure.Fields.List {
242
+ for _, name := range field.Names {
243
+ if name.Name != selector.Sel.Name {
244
+ continue
245
+ }
246
+ base.ConfigField = typ.name + "." + name.Name
247
+ base.Source = s.source(file, field.Pos()).String()
248
+ if field.Tag != nil {
249
+ tag, _ := strconv.Unquote(field.Tag.Value)
250
+ base.EnvironmentVariable = reflect.StructTag(tag).Get("envconfig")
251
+ if value, exists := reflect.StructTag(tag).Lookup("default"); exists {
252
+ base.Value = value
253
+ base.Kind = "config-default"
254
+ }
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ return base
262
+ }
263
+
264
+ func (s *scanner) destinationExprType(owner *functionDecl, expr ast.Expr) (endpointType, bool) {
265
+ if id, ok := expr.(*ast.Ident); ok {
266
+ if owner.fn.Type.Params != nil {
267
+ for _, field := range owner.fn.Type.Params.List {
268
+ for _, name := range field.Names {
269
+ if name.Name == id.Name {
270
+ return s.typeExpression(owner.file, field.Type)
271
+ }
272
+ }
273
+ }
274
+ }
275
+ typ, exists := s.localConcreteTypes(owner)[id.Name]
276
+ return typ, exists
277
+ }
278
+ if selector, ok := expr.(*ast.SelectorExpr); ok {
279
+ parent, exists := s.destinationExprType(owner, selector.X)
280
+ if !exists {
281
+ return endpointType{}, false
282
+ }
283
+ for _, file := range s.files {
284
+ if file.dir != parent.dir {
285
+ continue
286
+ }
287
+ for _, decl := range file.node.Decls {
288
+ gen, ok := decl.(*ast.GenDecl)
289
+ if !ok {
290
+ continue
291
+ }
292
+ for _, spec := range gen.Specs {
293
+ named, ok := spec.(*ast.TypeSpec)
294
+ if !ok || named.Name.Name != parent.name {
295
+ continue
296
+ }
297
+ structure, ok := named.Type.(*ast.StructType)
298
+ if !ok {
299
+ continue
300
+ }
301
+ for _, field := range structure.Fields.List {
302
+ for _, name := range field.Names {
303
+ if name.Name == selector.Sel.Name {
304
+ return s.typeExpression(file, field.Type)
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ types := s.fieldTypes[fieldTypeKey(parent, selector.Sel.Name)]
312
+ if len(types) == 1 {
313
+ return types[0], true
314
+ }
315
+ }
316
+ return endpointType{}, false
317
+ }
318
+
319
+ func (s *scanner) destinationFor(call Call, declaration *functionDecl, origins destinationObject) *catalog.HTTPDestination {
320
+ if call.Protocol != "HTTP" || call.template == nil {
321
+ return nil
322
+ }
323
+ d := &catalog.HTTPDestination{CallSite: call.Source.String(), EndpointExpression: expression(call.template.endpoint), Method: call.Method, LocalPath: call.Path}
324
+ // A recovered receiver binding only proves a join when source explicitly
325
+ // concatenates that field with a path. Later runtime transformations are
326
+ // retained separately; the full path describes this source-backed join.
327
+ receiver := receiverVariable(declaration.fn)
328
+ for field, base := range origins {
329
+ if strings.Contains(field, ".") {
330
+ continue
331
+ }
332
+ var join *ast.BinaryExpr
333
+ ast.Inspect(declaration.fn.Body, func(n ast.Node) bool {
334
+ binary, ok := n.(*ast.BinaryExpr)
335
+ if ok && binary.Op == token.ADD && expression(binary.X) == receiver+"."+field {
336
+ join = binary
337
+ }
338
+ return true
339
+ })
340
+ if join == nil {
341
+ continue
342
+ }
343
+ ast.Inspect(declaration.fn.Body, func(n ast.Node) bool {
344
+ assign, ok := n.(*ast.AssignStmt)
345
+ if !ok || assign.Pos() <= join.Pos() || assign.Pos() >= call.template.endpoint.Pos() || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 || expression(assign.Lhs[0]) != d.EndpointExpression {
346
+ return true
347
+ }
348
+ if _, ok := assign.Rhs[0].(*ast.CallExpr); ok {
349
+ d.Transforms = append(d.Transforms, catalog.HTTPDestinationJoin{Expression: expression(assign.Rhs[0]), Source: s.source(declaration.file, assign.Pos()).String()})
350
+ }
351
+ return true
352
+ })
353
+ d.BaseURL = &base
354
+ d.Join = &catalog.HTTPDestinationJoin{Expression: expression(join), Source: s.source(declaration.file, join.Pos()).String()}
355
+ if base.Value != "" && strings.HasPrefix(call.Endpoint, base.Value) {
356
+ d.LocalPath = strings.TrimPrefix(call.Endpoint, base.Value)
357
+ parsed, err := url.Parse(call.Endpoint)
358
+ if err == nil && parsed.Host != "" && parsed.Path != "" && strings.HasPrefix(d.LocalPath, "/") {
359
+ d.FullPath = parsed.Path
360
+ host := parsed.Hostname()
361
+ if host != "localhost" && host != "127.0.0.1" {
362
+ d.ServiceDiscoveryAlias = host
363
+ }
364
+ }
365
+ }
366
+ return d
367
+ }
368
+ if parsed, err := url.Parse(call.Endpoint); err == nil && parsed.Host != "" && (parsed.Scheme == "http" || parsed.Scheme == "https") {
369
+ d.FullPath = parsed.Path
370
+ d.ServiceDiscoveryAlias = parsed.Hostname()
371
+ }
372
+ return d
373
+ }