@shortlink-org/portolan 0.2.4 → 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.
- package/README.md +23 -0
- package/catalog/enum_test.go +46 -0
- package/catalog/evidence_test.go +35 -0
- package/catalog/model.go +1066 -0
- package/catalog/roundtrip_test.go +203 -0
- package/catalog/via_test.go +38 -0
- package/cli/init.test.mjs +6 -1
- package/cli/portolan.mjs +8 -0
- package/cli/portolan.test.mjs +49 -0
- package/go.mod +14 -0
- package/go.sum +20 -0
- package/internal/gocall/README.md +19 -0
- package/internal/gocall/analyze.go +189 -0
- package/internal/gocall/analyze_test.go +107 -0
- package/internal/gohttp/analyze.go +2562 -0
- package/internal/gohttp/destination.go +373 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +96 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/index.go +629 -0
- package/internal/goscan/index_test.go +66 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +37 -0
- package/internal/goscan/tree.go +284 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +70 -0
- package/internal/wsdl/parse.go +949 -0
- package/internal/wsdl/parse_test.go +170 -0
- package/package.json +22 -10
- package/plugin/describe.go +118 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +141 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +109 -46
- package/plugins/cmd/portolan-http-clients/main.go +19 -0
- package/plugins/extract-celery/extract.py +0 -2
- package/plugins/extract-celery/extract_test.py +1 -1
- package/plugins/extract-django/README.md +39 -17
- package/plugins/extract-django/domain.py +28 -17
- package/plugins/extract-django/extract.py +21 -7
- package/plugins/extract-django/extract_test.py +55 -2
- package/plugins/extract-django/lifecycle.py +2 -0
- package/plugins/extract-django/operations.py +1 -1
- package/plugins/extract-django/routing_test.py +109 -1
- package/plugins/extract-django/store.py +1 -1
- package/plugins/extract-django/transport.py +101 -55
- package/plugins/extract-django/verbs.py +241 -0
- package/plugins/extract-go/README.md +47 -0
- package/plugins/extract-http-clients/describe.go +19 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +740 -0
- package/plugins/extract-http-clients/extract_test.go +1561 -0
- package/plugins/extract-http-clients/main.go +41 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-python-kafka/extract.py +0 -2
- package/plugins/extract-python-kafka/extract_test.py +1 -1
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/extract.ts +4 -5
- package/plugins/extract-ts/graphql.test.ts +1 -1
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/protocol.py +1 -5
- package/portolan.json +3 -2
- package/schema/portolan.schema.json +34 -0
- package/scripts/README.md +18 -12
- package/scripts/catalog-sources.mjs +6 -0
- package/scripts/delivery-presets.mjs +21 -11
- package/scripts/diff.mjs +5 -1
- package/scripts/django-aggregates.test.mjs +58 -0
- package/scripts/gen-likec4.mjs +1 -1
- package/scripts/gen.mjs +118 -115
- package/scripts/go-discovery.test.mjs +30 -0
- package/scripts/history.mjs +186 -3
- package/scripts/history.test.mjs +1 -1
- package/scripts/host-plugins/fetch-git.mjs +77 -21
- package/scripts/host-plugins/fetch-git.test.mjs +62 -8
- package/scripts/local-api.mjs +71 -4
- package/scripts/local-api.test.mjs +63 -4
- package/scripts/local-discovery.mjs +82 -9
- package/scripts/manifest.mjs +5 -3
- package/scripts/manifest.test.mjs +24 -0
- package/scripts/output-diff.mjs +94 -0
- package/scripts/output-diff.test.mjs +36 -0
- package/scripts/package-smoke.mjs +62 -4
- package/scripts/plugin-host.mjs +22 -2
- package/scripts/plugin-host.test.mjs +9 -0
- package/scripts/plugin-wasm-worker.mjs +4 -1
- package/scripts/provenance.mjs +72 -0
- package/scripts/provenance.test.mjs +149 -0
- package/scripts/run-builtin.mjs +39 -5
- package/scripts/schema.mjs +29 -0
- package/scripts/warning-policy.mjs +161 -0
- package/scripts/warning-policy.test.mjs +56 -0
- package/src/app/Sidebar.tsx +3 -3
- package/src/catalog-docs.test.ts +64 -0
- package/src/catalog-docs.ts +35 -0
- package/src/catalog-error.test.ts +15 -0
- package/src/catalog-model.ts +48 -5
- package/src/catalog-validation.ts +9 -0
- package/src/chat/Starter.tsx +5 -11
- package/src/chat/tools.test.ts +27 -0
- package/src/chat/tools.ts +5 -9
- package/src/components/CatalogStamp.tsx +10 -8
- package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
- package/src/components/HTTPDestinationEvidence.tsx +31 -0
- package/src/components/Integrations.tsx +1 -1
- package/src/components/MachineDocs.tsx +6 -5
- package/src/components/MethodRows.tsx +9 -2
- package/src/components/RelationEvidence.test.tsx +14 -0
- package/src/components/RelationEvidence.tsx +53 -0
- package/src/data.ts +25 -7
- package/src/enrich.test.ts +332 -1
- package/src/enrich.ts +206 -3
- package/src/flow/StepDetail.tsx +6 -0
- package/src/flow/evidence.test.ts +16 -0
- package/src/flow/evidence.ts +34 -0
- package/src/index.css +44 -0
- package/src/landing/DraggableReveal.tsx +3 -2
- package/src/landing/EvidencePipeline.tsx +105 -0
- package/src/landing/LandingPage.tsx +2 -59
- package/src/lib/catalog-diff.ts +1 -1
- package/src/lib/django-aggregates.d.mts +9 -0
- package/src/lib/django-aggregates.mjs +36 -0
- package/src/lib/django-aggregates.test.ts +29 -0
- package/src/lib/django-aggregates.ts +5 -0
- package/src/lib/local-api.ts +20 -2
- package/src/lib/setup-info.test.ts +17 -0
- package/src/lib/setup-info.ts +58 -0
- package/src/lib/warnings.test.ts +54 -0
- package/src/lib/warnings.ts +260 -0
- package/src/map/ContextMapGraph.tsx +76 -32
- package/src/merge.ts +16 -9
- package/src/pages/AggregatePage.tsx +8 -7
- package/src/pages/ContextPage.tsx +6 -5
- package/src/pages/ServicePage.tsx +4 -3
- package/src/pages/Settings.tsx +189 -41
- package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
- package/src/selection/DetailPanel.tsx +15 -0
- package/src/virtual-provenance.d.ts +11 -0
- package/vite.config.ts +5 -0
- package/scripts/vendor-lock.mjs +0 -58
- package/scripts/vendor-lock.test.mjs +0 -69
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
package wsdl
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/xml"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"io/fs"
|
|
8
|
+
"net/url"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"sort"
|
|
12
|
+
"strings"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
const (
|
|
16
|
+
soap11WSDL = "http://schemas.xmlsoap.org/wsdl/soap/"
|
|
17
|
+
soap12WSDL = "http://schemas.xmlsoap.org/wsdl/soap12/"
|
|
18
|
+
xsdNS = "http://www.w3.org/2001/XMLSchema"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
type qname struct {
|
|
22
|
+
ns string
|
|
23
|
+
local string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type node struct {
|
|
27
|
+
name xml.Name
|
|
28
|
+
attrs []xml.Attr
|
|
29
|
+
ns map[string]string
|
|
30
|
+
text string
|
|
31
|
+
children []*node
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func (n *node) attr(name string) string {
|
|
35
|
+
for _, attr := range n.attrs {
|
|
36
|
+
if attr.Name.Local == name && attr.Name.Space != "xmlns" {
|
|
37
|
+
return strings.TrimSpace(attr.Value)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return ""
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func (n *node) child(name string) *node {
|
|
44
|
+
for _, child := range n.children {
|
|
45
|
+
if child.name.Local == name {
|
|
46
|
+
return child
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func (n *node) childrenNamed(name string) []*node {
|
|
53
|
+
var out []*node
|
|
54
|
+
for _, child := range n.children {
|
|
55
|
+
if child.name.Local == name {
|
|
56
|
+
out = append(out, child)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func (n *node) documentation() string {
|
|
63
|
+
doc := n.child("documentation")
|
|
64
|
+
if doc == nil {
|
|
65
|
+
annotation := n.child("annotation")
|
|
66
|
+
if annotation != nil {
|
|
67
|
+
doc = annotation.child("documentation")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if doc == nil {
|
|
71
|
+
return ""
|
|
72
|
+
}
|
|
73
|
+
return strings.Join(strings.Fields(doc.deepText()), " ")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func (n *node) deepText() string {
|
|
77
|
+
parts := []string{n.text}
|
|
78
|
+
for _, child := range n.children {
|
|
79
|
+
parts = append(parts, child.deepText())
|
|
80
|
+
}
|
|
81
|
+
return strings.Join(parts, " ")
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func parseXML(reader io.Reader) (*node, error) {
|
|
85
|
+
decoder := xml.NewDecoder(reader)
|
|
86
|
+
rootNS := map[string]string{"xml": "http://www.w3.org/XML/1998/namespace"}
|
|
87
|
+
var root *node
|
|
88
|
+
var stack []*node
|
|
89
|
+
for {
|
|
90
|
+
token, err := decoder.Token()
|
|
91
|
+
if err == io.EOF {
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
if err != nil {
|
|
95
|
+
return nil, err
|
|
96
|
+
}
|
|
97
|
+
switch value := token.(type) {
|
|
98
|
+
case xml.StartElement:
|
|
99
|
+
inScope := rootNS
|
|
100
|
+
if len(stack) > 0 {
|
|
101
|
+
inScope = stack[len(stack)-1].ns
|
|
102
|
+
}
|
|
103
|
+
namespaces := make(map[string]string, len(inScope)+2)
|
|
104
|
+
for prefix, namespace := range inScope {
|
|
105
|
+
namespaces[prefix] = namespace
|
|
106
|
+
}
|
|
107
|
+
for _, attr := range value.Attr {
|
|
108
|
+
switch {
|
|
109
|
+
case attr.Name.Space == "xmlns":
|
|
110
|
+
namespaces[attr.Name.Local] = attr.Value
|
|
111
|
+
case attr.Name.Space == "" && attr.Name.Local == "xmlns":
|
|
112
|
+
namespaces[""] = attr.Value
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
current := &node{name: value.Name, attrs: value.Attr, ns: namespaces}
|
|
116
|
+
if len(stack) == 0 {
|
|
117
|
+
root = current
|
|
118
|
+
} else {
|
|
119
|
+
parent := stack[len(stack)-1]
|
|
120
|
+
parent.children = append(parent.children, current)
|
|
121
|
+
}
|
|
122
|
+
stack = append(stack, current)
|
|
123
|
+
case xml.CharData:
|
|
124
|
+
if len(stack) > 0 {
|
|
125
|
+
stack[len(stack)-1].text += string(value)
|
|
126
|
+
}
|
|
127
|
+
case xml.EndElement:
|
|
128
|
+
if len(stack) > 0 {
|
|
129
|
+
stack = stack[:len(stack)-1]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if root == nil {
|
|
134
|
+
return nil, fmt.Errorf("empty XML document")
|
|
135
|
+
}
|
|
136
|
+
return root, nil
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type document struct {
|
|
140
|
+
path string
|
|
141
|
+
root *node
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type loader struct {
|
|
145
|
+
root string
|
|
146
|
+
docs map[string]*document
|
|
147
|
+
loaded map[string]bool
|
|
148
|
+
adopted map[string]map[string]bool
|
|
149
|
+
warnings []string
|
|
150
|
+
duplicates []duplicate
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// duplicate is one name declared again in a namespace: where the second
|
|
154
|
+
// declaration was found, and where the one the index kept came from.
|
|
155
|
+
type duplicate struct {
|
|
156
|
+
path string
|
|
157
|
+
name string
|
|
158
|
+
namespace string
|
|
159
|
+
origin string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Read loads one WSDL document and every local WSDL/XSD import or include it
|
|
163
|
+
// reaches. spec is relative to root.
|
|
164
|
+
func Read(root, spec string) (Result, error) {
|
|
165
|
+
absRoot, err := filepath.Abs(root)
|
|
166
|
+
if err != nil {
|
|
167
|
+
return Result{}, err
|
|
168
|
+
}
|
|
169
|
+
l := newLoader(absRoot)
|
|
170
|
+
path, err := l.localPath(absRoot, spec)
|
|
171
|
+
if err != nil {
|
|
172
|
+
return Result{}, err
|
|
173
|
+
}
|
|
174
|
+
if err := l.load(path, ""); err != nil {
|
|
175
|
+
return Result{}, err
|
|
176
|
+
}
|
|
177
|
+
result := l.result([]string{path}, true)
|
|
178
|
+
return result, nil
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Discover reads every WSDL in root. Imported interface-only documents are
|
|
182
|
+
// used to complete their parent and are not emitted a second time.
|
|
183
|
+
func Discover(root string) (Result, error) {
|
|
184
|
+
absRoot, err := filepath.Abs(root)
|
|
185
|
+
if err != nil {
|
|
186
|
+
return Result{}, err
|
|
187
|
+
}
|
|
188
|
+
var candidates []string
|
|
189
|
+
err = filepath.WalkDir(absRoot, func(path string, entry fs.DirEntry, walkErr error) error {
|
|
190
|
+
if walkErr != nil {
|
|
191
|
+
return walkErr
|
|
192
|
+
}
|
|
193
|
+
if entry.IsDir() {
|
|
194
|
+
if path != absRoot && (strings.HasPrefix(entry.Name(), ".") || entry.Name() == "node_modules") {
|
|
195
|
+
return filepath.SkipDir
|
|
196
|
+
}
|
|
197
|
+
return nil
|
|
198
|
+
}
|
|
199
|
+
if strings.EqualFold(filepath.Ext(entry.Name()), ".wsdl") {
|
|
200
|
+
candidates = append(candidates, path)
|
|
201
|
+
}
|
|
202
|
+
return nil
|
|
203
|
+
})
|
|
204
|
+
if err != nil {
|
|
205
|
+
return Result{}, err
|
|
206
|
+
}
|
|
207
|
+
sort.Strings(candidates)
|
|
208
|
+
var roots []string
|
|
209
|
+
for _, path := range candidates {
|
|
210
|
+
file, openErr := os.Open(path)
|
|
211
|
+
if openErr != nil {
|
|
212
|
+
return Result{}, openErr
|
|
213
|
+
}
|
|
214
|
+
node, parseErr := parseXML(file)
|
|
215
|
+
_ = file.Close()
|
|
216
|
+
if parseErr != nil {
|
|
217
|
+
return Result{}, fmt.Errorf("%s: %w", path, parseErr)
|
|
218
|
+
}
|
|
219
|
+
if node.name.Local == "definitions" && len(node.childrenNamed("service")) > 0 {
|
|
220
|
+
roots = append(roots, path)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
standalone := false
|
|
224
|
+
if len(roots) == 0 {
|
|
225
|
+
// Some repositories keep only the reusable portType document. It is
|
|
226
|
+
// still a useful contract when no service document exists to own it.
|
|
227
|
+
roots = candidates
|
|
228
|
+
standalone = true
|
|
229
|
+
}
|
|
230
|
+
combined := Result{}
|
|
231
|
+
for _, path := range roots {
|
|
232
|
+
l := newLoader(absRoot)
|
|
233
|
+
if err := l.load(path, ""); err != nil {
|
|
234
|
+
return Result{}, err
|
|
235
|
+
}
|
|
236
|
+
part := l.result([]string{path}, standalone)
|
|
237
|
+
combined.Contracts = append(combined.Contracts, part.Contracts...)
|
|
238
|
+
combined.Warnings = append(combined.Warnings, part.Warnings...)
|
|
239
|
+
}
|
|
240
|
+
combined.Contracts = uniqueContracts(combined.Contracts)
|
|
241
|
+
combined.Warnings = unique(combined.Warnings)
|
|
242
|
+
sort.Slice(combined.Contracts, func(i, j int) bool {
|
|
243
|
+
if combined.Contracts[i].Name != combined.Contracts[j].Name {
|
|
244
|
+
return combined.Contracts[i].Name < combined.Contracts[j].Name
|
|
245
|
+
}
|
|
246
|
+
return combined.Contracts[i].Source < combined.Contracts[j].Source
|
|
247
|
+
})
|
|
248
|
+
sort.Strings(combined.Warnings)
|
|
249
|
+
return combined, nil
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
func newLoader(root string) *loader {
|
|
253
|
+
return &loader{
|
|
254
|
+
root: root, docs: map[string]*document{}, loaded: map[string]bool{},
|
|
255
|
+
adopted: map[string]map[string]bool{},
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
func (l *loader) localPath(from, location string) (string, error) {
|
|
260
|
+
if strings.TrimSpace(location) == "" {
|
|
261
|
+
return "", fmt.Errorf("empty WSDL path")
|
|
262
|
+
}
|
|
263
|
+
if parsed, err := url.Parse(location); err == nil && parsed.Scheme != "" {
|
|
264
|
+
return "", fmt.Errorf("remote import %q is not read", location)
|
|
265
|
+
}
|
|
266
|
+
path := location
|
|
267
|
+
if !filepath.IsAbs(path) {
|
|
268
|
+
path = filepath.Join(from, filepath.FromSlash(path))
|
|
269
|
+
}
|
|
270
|
+
path, err := filepath.Abs(filepath.Clean(path))
|
|
271
|
+
if err != nil {
|
|
272
|
+
return "", err
|
|
273
|
+
}
|
|
274
|
+
rel, err := filepath.Rel(l.root, path)
|
|
275
|
+
if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
|
276
|
+
return "", fmt.Errorf("import %q leaves the input root", location)
|
|
277
|
+
}
|
|
278
|
+
return path, nil
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
func (l *loader) load(path, adoptedNS string) error {
|
|
282
|
+
key := path + "\x00" + adoptedNS
|
|
283
|
+
if l.loaded[key] {
|
|
284
|
+
return nil
|
|
285
|
+
}
|
|
286
|
+
l.loaded[key] = true
|
|
287
|
+
doc := l.docs[path]
|
|
288
|
+
if doc == nil {
|
|
289
|
+
file, err := os.Open(path)
|
|
290
|
+
if err != nil {
|
|
291
|
+
return err
|
|
292
|
+
}
|
|
293
|
+
root, parseErr := parseXML(file)
|
|
294
|
+
closeErr := file.Close()
|
|
295
|
+
if parseErr != nil {
|
|
296
|
+
return fmt.Errorf("%s: %w", path, parseErr)
|
|
297
|
+
}
|
|
298
|
+
if closeErr != nil {
|
|
299
|
+
return closeErr
|
|
300
|
+
}
|
|
301
|
+
doc = &document{path: path, root: root}
|
|
302
|
+
l.docs[path] = doc
|
|
303
|
+
}
|
|
304
|
+
if doc.root.name.Local == "schema" && doc.root.attr("targetNamespace") == "" && adoptedNS != "" {
|
|
305
|
+
if l.adopted[path] == nil {
|
|
306
|
+
l.adopted[path] = map[string]bool{}
|
|
307
|
+
}
|
|
308
|
+
l.adopted[path][adoptedNS] = true
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for _, ref := range importReferences(doc.root, adoptedNS) {
|
|
312
|
+
imported, err := l.localPath(filepath.Dir(path), ref.location)
|
|
313
|
+
if err != nil {
|
|
314
|
+
rel, _ := filepath.Rel(l.root, path)
|
|
315
|
+
l.warnings = append(l.warnings, filepath.ToSlash(rel)+": "+err.Error())
|
|
316
|
+
continue
|
|
317
|
+
}
|
|
318
|
+
if err := l.load(imported, ref.adoptedNS); err != nil {
|
|
319
|
+
rel, _ := filepath.Rel(l.root, path)
|
|
320
|
+
l.warnings = append(l.warnings, filepath.ToSlash(rel)+": import "+ref.location+": "+err.Error())
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return nil
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
type importReference struct {
|
|
327
|
+
location string
|
|
328
|
+
adoptedNS string
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
func importReferences(root *node, inheritedNS string) []importReference {
|
|
332
|
+
var out []importReference
|
|
333
|
+
var walk func(*node, string)
|
|
334
|
+
walk = func(n *node, schemaNS string) {
|
|
335
|
+
if n.name.Local == "schema" {
|
|
336
|
+
schemaNS = first(n.attr("targetNamespace"), schemaNS)
|
|
337
|
+
}
|
|
338
|
+
if n.name.Local == "import" || n.name.Local == "include" || n.name.Local == "redefine" {
|
|
339
|
+
if location := first(n.attr("location"), n.attr("schemaLocation")); location != "" {
|
|
340
|
+
adopted := ""
|
|
341
|
+
if n.name.Local == "include" || n.name.Local == "redefine" {
|
|
342
|
+
adopted = schemaNS
|
|
343
|
+
}
|
|
344
|
+
out = append(out, importReference{location: location, adoptedNS: adopted})
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
for _, child := range n.children {
|
|
348
|
+
walk(child, schemaNS)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
walk(root, inheritedNS)
|
|
352
|
+
seen := map[string]bool{}
|
|
353
|
+
var uniqueRefs []importReference
|
|
354
|
+
for _, ref := range out {
|
|
355
|
+
key := ref.location + "\x00" + ref.adoptedNS
|
|
356
|
+
if !seen[key] {
|
|
357
|
+
uniqueRefs = append(uniqueRefs, ref)
|
|
358
|
+
seen[key] = true
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return uniqueRefs
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
type index struct {
|
|
365
|
+
messages map[qname]*node
|
|
366
|
+
portTypes map[qname]*node
|
|
367
|
+
bindings map[qname]*node
|
|
368
|
+
elements map[qname]*node
|
|
369
|
+
complexTypes map[qname]*node
|
|
370
|
+
namespace map[*node]string
|
|
371
|
+
// origin is the document each indexed node was read from, so a duplicate
|
|
372
|
+
// can say which declaration won.
|
|
373
|
+
origin map[*node]string
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
func (l *loader) buildIndex() index {
|
|
377
|
+
ix := index{
|
|
378
|
+
messages: map[qname]*node{}, portTypes: map[qname]*node{}, bindings: map[qname]*node{},
|
|
379
|
+
elements: map[qname]*node{}, complexTypes: map[qname]*node{}, namespace: map[*node]string{},
|
|
380
|
+
origin: map[*node]string{},
|
|
381
|
+
}
|
|
382
|
+
paths := make([]string, 0, len(l.docs))
|
|
383
|
+
for path := range l.docs {
|
|
384
|
+
paths = append(paths, path)
|
|
385
|
+
}
|
|
386
|
+
sort.Strings(paths)
|
|
387
|
+
for _, path := range paths {
|
|
388
|
+
root := l.docs[path].root
|
|
389
|
+
source := l.relative(path)
|
|
390
|
+
switch root.name.Local {
|
|
391
|
+
case "definitions":
|
|
392
|
+
ns := root.attr("targetNamespace")
|
|
393
|
+
ix.namespace[root] = ns
|
|
394
|
+
for _, child := range root.children {
|
|
395
|
+
switch child.name.Local {
|
|
396
|
+
case "message":
|
|
397
|
+
ix.put(ix.messages, qname{ns, child.attr("name")}, child, &l.duplicates, source)
|
|
398
|
+
case "portType", "interface":
|
|
399
|
+
ix.put(ix.portTypes, qname{ns, child.attr("name")}, child, &l.duplicates, source)
|
|
400
|
+
case "binding":
|
|
401
|
+
ix.put(ix.bindings, qname{ns, child.attr("name")}, child, &l.duplicates, source)
|
|
402
|
+
case "types":
|
|
403
|
+
for _, schema := range child.childrenNamed("schema") {
|
|
404
|
+
ix.addSchema(schema, source, "", &l.duplicates)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
case "schema":
|
|
409
|
+
namespaces := []string{root.attr("targetNamespace")}
|
|
410
|
+
if namespaces[0] == "" && len(l.adopted[path]) > 0 {
|
|
411
|
+
namespaces = namespaces[:0]
|
|
412
|
+
for namespace := range l.adopted[path] {
|
|
413
|
+
namespaces = append(namespaces, namespace)
|
|
414
|
+
}
|
|
415
|
+
sort.Strings(namespaces)
|
|
416
|
+
}
|
|
417
|
+
for _, namespace := range namespaces {
|
|
418
|
+
ix.addSchema(root, source, namespace, &l.duplicates)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return ix
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
func (ix *index) addSchema(schema *node, path, adoptedNS string, duplicates *[]duplicate) {
|
|
426
|
+
ns := first(schema.attr("targetNamespace"), adoptedNS)
|
|
427
|
+
ix.namespace[schema] = ns
|
|
428
|
+
for _, child := range schema.children {
|
|
429
|
+
switch child.name.Local {
|
|
430
|
+
case "element":
|
|
431
|
+
ix.put(ix.elements, qname{ns, child.attr("name")}, child, duplicates, path)
|
|
432
|
+
case "complexType":
|
|
433
|
+
ix.put(ix.complexTypes, qname{ns, child.attr("name")}, child, duplicates, path)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// put keeps the first declaration of a name and records every later one.
|
|
439
|
+
// Documents are indexed in path order, so "first" is the alphabetically
|
|
440
|
+
// earliest file, and the same file read under two adopted namespaces is
|
|
441
|
+
// the same node and not a duplicate of itself.
|
|
442
|
+
func (ix *index) put(target map[qname]*node, key qname, value *node, duplicates *[]duplicate, path string) {
|
|
443
|
+
if key.local == "" {
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
if previous, exists := target[key]; exists && previous != value {
|
|
447
|
+
*duplicates = append(*duplicates, duplicate{path: path, name: key.local, namespace: key.ns, origin: ix.origin[previous]})
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
target[key] = value
|
|
451
|
+
ix.origin[value] = path
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
func (l *loader) result(rootPaths []string, includeStandalone bool) Result {
|
|
455
|
+
ix := l.buildIndex()
|
|
456
|
+
var contracts []Contract
|
|
457
|
+
for _, path := range rootPaths {
|
|
458
|
+
doc := l.docs[path]
|
|
459
|
+
if doc == nil || doc.root.name.Local != "definitions" {
|
|
460
|
+
continue
|
|
461
|
+
}
|
|
462
|
+
definitions := doc.root
|
|
463
|
+
namespace := definitions.attr("targetNamespace")
|
|
464
|
+
services := definitions.childrenNamed("service")
|
|
465
|
+
if len(services) == 0 && includeStandalone {
|
|
466
|
+
contract := Contract{Name: first(definitions.attr("name"), strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))), Namespace: namespace, Summary: definitions.documentation(), Source: l.relative(path)}
|
|
467
|
+
for _, portType := range definitions.childrenNamed("portType") {
|
|
468
|
+
contract.Interfaces = append(contract.Interfaces, ix.abstractInterface(portType))
|
|
469
|
+
}
|
|
470
|
+
if len(contract.Interfaces) > 0 {
|
|
471
|
+
contracts = append(contracts, contract)
|
|
472
|
+
}
|
|
473
|
+
continue
|
|
474
|
+
}
|
|
475
|
+
for _, service := range services {
|
|
476
|
+
contract := Contract{Name: first(service.attr("name"), definitions.attr("name")), Namespace: namespace, Summary: first(service.documentation(), definitions.documentation()), Source: l.relative(path)}
|
|
477
|
+
for _, port := range service.childrenNamed("port") {
|
|
478
|
+
if iface, ok := ix.boundInterface(port); ok {
|
|
479
|
+
contract.Interfaces = append(contract.Interfaces, iface)
|
|
480
|
+
} else {
|
|
481
|
+
l.warnings = append(l.warnings, l.relative(path)+": port "+first(port.attr("name"), "<unnamed>")+" refers to a binding or portType that is not available locally")
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if len(contract.Interfaces) > 0 {
|
|
485
|
+
contracts = append(contracts, contract)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
contracts = uniqueContracts(contracts)
|
|
490
|
+
for i := range contracts {
|
|
491
|
+
sort.Slice(contracts[i].Interfaces, func(a, b int) bool { return contracts[i].Interfaces[a].Name < contracts[i].Interfaces[b].Name })
|
|
492
|
+
}
|
|
493
|
+
sort.Slice(contracts, func(i, j int) bool {
|
|
494
|
+
if contracts[i].Name != contracts[j].Name {
|
|
495
|
+
return contracts[i].Name < contracts[j].Name
|
|
496
|
+
}
|
|
497
|
+
return contracts[i].Source < contracts[j].Source
|
|
498
|
+
})
|
|
499
|
+
sort.Strings(l.warnings)
|
|
500
|
+
return Result{Contracts: contracts, Warnings: unique(l.warnings), SchemaWarnings: summarizeDuplicates(l.duplicates)}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// summarizeDuplicates says once per namespace and winning document which
|
|
504
|
+
// names were declared again and where. A schema copied beside every WSDL that
|
|
505
|
+
// uses it is one finding, not one per type; the reader fixing it wants the
|
|
506
|
+
// file that is kept, the files that repeat it, and enough names to recognise
|
|
507
|
+
// the copy.
|
|
508
|
+
func summarizeDuplicates(duplicates []duplicate) []string {
|
|
509
|
+
type group struct {
|
|
510
|
+
namespace string
|
|
511
|
+
origin string
|
|
512
|
+
names []string
|
|
513
|
+
paths []string
|
|
514
|
+
}
|
|
515
|
+
groups := map[string]*group{}
|
|
516
|
+
var keys []string
|
|
517
|
+
for _, d := range duplicates {
|
|
518
|
+
key := d.namespace + "\x00" + d.origin
|
|
519
|
+
found := groups[key]
|
|
520
|
+
if found == nil {
|
|
521
|
+
found = &group{namespace: d.namespace, origin: d.origin}
|
|
522
|
+
groups[key] = found
|
|
523
|
+
keys = append(keys, key)
|
|
524
|
+
}
|
|
525
|
+
found.names = append(found.names, d.name)
|
|
526
|
+
found.paths = append(found.paths, d.path)
|
|
527
|
+
}
|
|
528
|
+
sort.Strings(keys)
|
|
529
|
+
var out []string
|
|
530
|
+
for _, key := range keys {
|
|
531
|
+
found := groups[key]
|
|
532
|
+
names := unique(found.names)
|
|
533
|
+
paths := unique(found.paths)
|
|
534
|
+
sort.Strings(paths)
|
|
535
|
+
var b strings.Builder
|
|
536
|
+
b.WriteString(paths[0] + ": duplicate declaration " + names[0])
|
|
537
|
+
if len(names) > 1 {
|
|
538
|
+
fmt.Fprintf(&b, " and %d more", len(names)-1)
|
|
539
|
+
}
|
|
540
|
+
b.WriteString(" in namespace " + found.namespace)
|
|
541
|
+
if len(paths) > 1 {
|
|
542
|
+
b.WriteString(" (also in " + strings.Join(paths[1:], ", ") + ")")
|
|
543
|
+
}
|
|
544
|
+
if len(names) > 1 {
|
|
545
|
+
b.WriteString("; the declarations in " + found.origin + " are used")
|
|
546
|
+
} else {
|
|
547
|
+
b.WriteString("; the declaration in " + found.origin + " is used")
|
|
548
|
+
}
|
|
549
|
+
out = append(out, b.String())
|
|
550
|
+
}
|
|
551
|
+
return out
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
func (l *loader) relative(path string) string {
|
|
555
|
+
rel, err := filepath.Rel(l.root, path)
|
|
556
|
+
if err != nil {
|
|
557
|
+
return filepath.ToSlash(path)
|
|
558
|
+
}
|
|
559
|
+
return filepath.ToSlash(rel)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
func (ix index) abstractInterface(portType *node) Interface {
|
|
563
|
+
iface := Interface{Name: portType.attr("name"), PortType: portType.attr("name")}
|
|
564
|
+
iface.Operations, iface.Messages = ix.operations(portType, nil)
|
|
565
|
+
return iface
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
func (ix index) boundInterface(port *node) (Interface, bool) {
|
|
569
|
+
bindingRef := resolveQName(port, port.attr("binding"), "")
|
|
570
|
+
binding := ix.lookup(ix.bindings, bindingRef)
|
|
571
|
+
if binding == nil {
|
|
572
|
+
return Interface{}, false
|
|
573
|
+
}
|
|
574
|
+
portTypeRef := resolveQName(binding, binding.attr("type"), bindingRef.ns)
|
|
575
|
+
portType := ix.lookup(ix.portTypes, portTypeRef)
|
|
576
|
+
if portType == nil {
|
|
577
|
+
return Interface{}, false
|
|
578
|
+
}
|
|
579
|
+
version, style := bindingSOAP(binding)
|
|
580
|
+
endpoint := ""
|
|
581
|
+
for _, child := range port.children {
|
|
582
|
+
if child.name.Local == "address" && (child.name.Space == soap11WSDL || child.name.Space == soap12WSDL || strings.Contains(child.name.Space, "soap")) {
|
|
583
|
+
endpoint = child.attr("location")
|
|
584
|
+
if child.name.Space == soap12WSDL {
|
|
585
|
+
version = "1.2"
|
|
586
|
+
} else if version == "" {
|
|
587
|
+
version = "1.1"
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
iface := Interface{Name: first(port.attr("name"), binding.attr("name")), Binding: binding.attr("name"), PortType: portType.attr("name"), Endpoint: endpoint, Version: version, Style: style}
|
|
592
|
+
iface.Operations, iface.Messages = ix.operations(portType, binding)
|
|
593
|
+
return iface, true
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
func bindingSOAP(binding *node) (version, style string) {
|
|
597
|
+
for _, child := range binding.children {
|
|
598
|
+
if child.name.Local != "binding" {
|
|
599
|
+
continue
|
|
600
|
+
}
|
|
601
|
+
if child.name.Space == soap12WSDL {
|
|
602
|
+
version = "1.2"
|
|
603
|
+
} else if child.name.Space == soap11WSDL || strings.Contains(child.name.Space, "soap") {
|
|
604
|
+
version = "1.1"
|
|
605
|
+
}
|
|
606
|
+
style = child.attr("style")
|
|
607
|
+
}
|
|
608
|
+
return version, style
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
func (ix index) operations(portType, binding *node) ([]Operation, []Message) {
|
|
612
|
+
bindingOps := map[string]*node{}
|
|
613
|
+
if binding != nil {
|
|
614
|
+
for _, operation := range binding.childrenNamed("operation") {
|
|
615
|
+
bindingOps[operation.attr("name")] = operation
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
var operations []Operation
|
|
619
|
+
messageRefs := map[qname]bool{}
|
|
620
|
+
for _, abstract := range portType.childrenNamed("operation") {
|
|
621
|
+
name := abstract.attr("name")
|
|
622
|
+
bound := bindingOps[name]
|
|
623
|
+
operation := Operation{Name: name, Doc: abstract.documentation()}
|
|
624
|
+
if input := abstract.child("input"); input != nil {
|
|
625
|
+
ref := resolveQName(input, input.attr("message"), "")
|
|
626
|
+
operation.Request = ref.local
|
|
627
|
+
messageRefs[ref] = ref.local != ""
|
|
628
|
+
}
|
|
629
|
+
if output := abstract.child("output"); output != nil {
|
|
630
|
+
ref := resolveQName(output, output.attr("message"), "")
|
|
631
|
+
operation.Response = ref.local
|
|
632
|
+
messageRefs[ref] = ref.local != ""
|
|
633
|
+
}
|
|
634
|
+
for _, fault := range abstract.childrenNamed("fault") {
|
|
635
|
+
ref := resolveQName(fault, fault.attr("message"), "")
|
|
636
|
+
faultName := first(fault.attr("name"), ref.local)
|
|
637
|
+
if faultName != "" {
|
|
638
|
+
operation.Faults = append(operation.Faults, faultName)
|
|
639
|
+
}
|
|
640
|
+
messageRefs[ref] = ref.local != ""
|
|
641
|
+
}
|
|
642
|
+
if bound != nil {
|
|
643
|
+
for _, child := range bound.children {
|
|
644
|
+
if child.name.Local == "operation" && (child.name.Space == soap11WSDL || child.name.Space == soap12WSDL || strings.Contains(child.name.Space, "soap")) {
|
|
645
|
+
operation.Action = first(child.attr("soapAction"), child.attr("soapActionURI"))
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
operation.Headers = bindingHeaders(bound)
|
|
649
|
+
if operation.Doc == "" {
|
|
650
|
+
operation.Doc = bound.documentation()
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
operation.Faults = unique(operation.Faults)
|
|
654
|
+
operations = append(operations, operation)
|
|
655
|
+
}
|
|
656
|
+
var messages []Message
|
|
657
|
+
seen := map[string]bool{}
|
|
658
|
+
var typeQueue []qname
|
|
659
|
+
refs := make([]qname, 0, len(messageRefs))
|
|
660
|
+
for ref, present := range messageRefs {
|
|
661
|
+
if present {
|
|
662
|
+
refs = append(refs, ref)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
sort.Slice(refs, func(i, j int) bool { return qnameLess(refs[i], refs[j]) })
|
|
666
|
+
for _, ref := range refs {
|
|
667
|
+
message, reached := ix.message(ref)
|
|
668
|
+
if message.Name != "" && !seen[message.Name] {
|
|
669
|
+
messages = append(messages, message)
|
|
670
|
+
seen[message.Name] = true
|
|
671
|
+
}
|
|
672
|
+
typeQueue = append(typeQueue, reached...)
|
|
673
|
+
}
|
|
674
|
+
for len(typeQueue) > 0 {
|
|
675
|
+
ref := typeQueue[0]
|
|
676
|
+
typeQueue = typeQueue[1:]
|
|
677
|
+
if ref.local == "" || seen[ref.local] || builtin(ref) {
|
|
678
|
+
continue
|
|
679
|
+
}
|
|
680
|
+
complex := ix.lookup(ix.complexTypes, ref)
|
|
681
|
+
if complex == nil {
|
|
682
|
+
continue
|
|
683
|
+
}
|
|
684
|
+
fields, reached := ix.shape(complex, map[qname]bool{})
|
|
685
|
+
messages = append(messages, Message{Name: ref.local, Fields: fields})
|
|
686
|
+
seen[ref.local] = true
|
|
687
|
+
typeQueue = append(typeQueue, reached...)
|
|
688
|
+
}
|
|
689
|
+
sort.Slice(operations, func(i, j int) bool { return operations[i].Name < operations[j].Name })
|
|
690
|
+
sort.Slice(messages, func(i, j int) bool { return messages[i].Name < messages[j].Name })
|
|
691
|
+
return operations, messages
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
func bindingHeaders(operation *node) []string {
|
|
695
|
+
var headers []string
|
|
696
|
+
for _, direction := range []string{"input", "output"} {
|
|
697
|
+
container := operation.child(direction)
|
|
698
|
+
if container == nil {
|
|
699
|
+
continue
|
|
700
|
+
}
|
|
701
|
+
for _, child := range container.children {
|
|
702
|
+
if child.name.Local != "header" || !(child.name.Space == soap11WSDL || child.name.Space == soap12WSDL || strings.Contains(child.name.Space, "soap")) {
|
|
703
|
+
continue
|
|
704
|
+
}
|
|
705
|
+
message := resolveQName(child, child.attr("message"), "").local
|
|
706
|
+
header := first(child.attr("part"), message)
|
|
707
|
+
if message != "" && child.attr("part") != "" {
|
|
708
|
+
header = message + "." + child.attr("part")
|
|
709
|
+
}
|
|
710
|
+
if header != "" {
|
|
711
|
+
headers = append(headers, header)
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return unique(headers)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
func (ix index) message(ref qname) (Message, []qname) {
|
|
719
|
+
messageNode := ix.lookup(ix.messages, ref)
|
|
720
|
+
if messageNode == nil {
|
|
721
|
+
return Message{Name: ref.local}, nil
|
|
722
|
+
}
|
|
723
|
+
message := Message{Name: messageNode.attr("name")}
|
|
724
|
+
var reached []qname
|
|
725
|
+
parts := messageNode.childrenNamed("part")
|
|
726
|
+
for _, part := range parts {
|
|
727
|
+
if elementValue := part.attr("element"); elementValue != "" {
|
|
728
|
+
elementRef := resolveQName(part, elementValue, ref.ns)
|
|
729
|
+
element := ix.lookup(ix.elements, elementRef)
|
|
730
|
+
if element != nil && len(parts) == 1 {
|
|
731
|
+
fields, refs := ix.elementShape(element, elementRef.ns)
|
|
732
|
+
if len(fields) > 0 {
|
|
733
|
+
message.Fields = append(message.Fields, fields...)
|
|
734
|
+
reached = append(reached, refs...)
|
|
735
|
+
continue
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
typeName := elementRef.local
|
|
739
|
+
if element != nil && element.attr("type") != "" {
|
|
740
|
+
typeRef := resolveQName(element, element.attr("type"), elementRef.ns)
|
|
741
|
+
typeName = typeLabel(typeRef)
|
|
742
|
+
reached = append(reached, typeRef)
|
|
743
|
+
}
|
|
744
|
+
message.Fields = append(message.Fields, Field{Name: first(part.attr("name"), elementRef.local), Type: typeName, Doc: documentationOf(element)})
|
|
745
|
+
continue
|
|
746
|
+
}
|
|
747
|
+
typeRef := resolveQName(part, part.attr("type"), ref.ns)
|
|
748
|
+
message.Fields = append(message.Fields, Field{Name: first(part.attr("name"), "value"), Type: typeLabel(typeRef), Doc: part.documentation()})
|
|
749
|
+
reached = append(reached, typeRef)
|
|
750
|
+
}
|
|
751
|
+
return message, reached
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
func (ix index) elementShape(element *node, fallbackNS string) ([]Field, []qname) {
|
|
755
|
+
if inline := element.child("complexType"); inline != nil {
|
|
756
|
+
return ix.shape(inline, map[qname]bool{})
|
|
757
|
+
}
|
|
758
|
+
typeRef := resolveQName(element, element.attr("type"), fallbackNS)
|
|
759
|
+
if complex := ix.lookup(ix.complexTypes, typeRef); complex != nil {
|
|
760
|
+
fields, refs := ix.shape(complex, map[qname]bool{typeRef: true})
|
|
761
|
+
return fields, append(refs, typeRef)
|
|
762
|
+
}
|
|
763
|
+
return nil, []qname{typeRef}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
func (ix index) shape(complex *node, resolving map[qname]bool) ([]Field, []qname) {
|
|
767
|
+
var fields []Field
|
|
768
|
+
var refs []qname
|
|
769
|
+
var walk func(*node)
|
|
770
|
+
walk = func(current *node) {
|
|
771
|
+
for _, child := range current.children {
|
|
772
|
+
switch child.name.Local {
|
|
773
|
+
case "extension", "restriction":
|
|
774
|
+
base := resolveQName(child, child.attr("base"), "")
|
|
775
|
+
if !builtin(base) && !resolving[base] {
|
|
776
|
+
if parent := ix.lookup(ix.complexTypes, base); parent != nil {
|
|
777
|
+
resolving[base] = true
|
|
778
|
+
parentFields, parentRefs := ix.shape(parent, resolving)
|
|
779
|
+
delete(resolving, base)
|
|
780
|
+
fields = append(fields, parentFields...)
|
|
781
|
+
refs = append(refs, parentRefs...)
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
walk(child)
|
|
785
|
+
case "sequence", "all", "choice", "complexContent", "simpleContent", "group":
|
|
786
|
+
walk(child)
|
|
787
|
+
case "element":
|
|
788
|
+
name := child.attr("name")
|
|
789
|
+
ref := resolveQName(child, child.attr("ref"), "")
|
|
790
|
+
if name == "" {
|
|
791
|
+
name = ref.local
|
|
792
|
+
}
|
|
793
|
+
typeRef := resolveQName(child, child.attr("type"), "")
|
|
794
|
+
if typeRef.local == "" && ref.local != "" {
|
|
795
|
+
if declared := ix.lookup(ix.elements, ref); declared != nil {
|
|
796
|
+
typeRef = resolveQName(declared, declared.attr("type"), ref.ns)
|
|
797
|
+
if name == "" {
|
|
798
|
+
name = declared.attr("name")
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
typeName := typeLabel(typeRef)
|
|
803
|
+
if inline := child.child("complexType"); inline != nil {
|
|
804
|
+
typeName = first(name, "object")
|
|
805
|
+
}
|
|
806
|
+
if typeName == "" {
|
|
807
|
+
typeName = "any"
|
|
808
|
+
}
|
|
809
|
+
if max := child.attr("maxOccurs"); max == "unbounded" || (max != "" && max != "1") {
|
|
810
|
+
typeName += "[]"
|
|
811
|
+
}
|
|
812
|
+
fields = append(fields, Field{Name: name, Type: typeName, Doc: child.documentation()})
|
|
813
|
+
refs = append(refs, typeRef)
|
|
814
|
+
case "attribute":
|
|
815
|
+
name := first(child.attr("name"), resolveQName(child, child.attr("ref"), "").local)
|
|
816
|
+
typeRef := resolveQName(child, child.attr("type"), "")
|
|
817
|
+
fields = append(fields, Field{Name: "@" + name, Type: first(typeLabel(typeRef), "string"), Doc: child.documentation()})
|
|
818
|
+
refs = append(refs, typeRef)
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
walk(complex)
|
|
823
|
+
return uniqueFields(fields), refs
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
func (ix index) lookup(values map[qname]*node, ref qname) *node {
|
|
827
|
+
if found := values[ref]; found != nil {
|
|
828
|
+
return found
|
|
829
|
+
}
|
|
830
|
+
if ref.local == "" {
|
|
831
|
+
return nil
|
|
832
|
+
}
|
|
833
|
+
var match *node
|
|
834
|
+
for key, value := range values {
|
|
835
|
+
if key.local != ref.local {
|
|
836
|
+
continue
|
|
837
|
+
}
|
|
838
|
+
if match != nil {
|
|
839
|
+
return nil
|
|
840
|
+
}
|
|
841
|
+
match = value
|
|
842
|
+
}
|
|
843
|
+
return match
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
func resolveQName(n *node, value, fallbackNS string) qname {
|
|
847
|
+
value = strings.TrimSpace(value)
|
|
848
|
+
if value == "" {
|
|
849
|
+
return qname{}
|
|
850
|
+
}
|
|
851
|
+
if prefix, local, ok := strings.Cut(value, ":"); ok {
|
|
852
|
+
return qname{ns: n.ns[prefix], local: local}
|
|
853
|
+
}
|
|
854
|
+
namespace := n.ns[""]
|
|
855
|
+
if namespace == "" || namespace == "http://schemas.xmlsoap.org/wsdl/" {
|
|
856
|
+
namespace = fallbackNS
|
|
857
|
+
}
|
|
858
|
+
return qname{ns: namespace, local: value}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
func typeLabel(ref qname) string {
|
|
862
|
+
if ref.local == "" {
|
|
863
|
+
return ""
|
|
864
|
+
}
|
|
865
|
+
if ref.ns == xsdNS || strings.Contains(strings.ToLower(ref.ns), "xmlschema") {
|
|
866
|
+
switch strings.ToLower(ref.local) {
|
|
867
|
+
case "integer", "int", "long", "short", "byte", "nonnegativeinteger", "positiveinteger":
|
|
868
|
+
return "int"
|
|
869
|
+
case "decimal", "double", "float":
|
|
870
|
+
return "number"
|
|
871
|
+
case "boolean":
|
|
872
|
+
return "boolean"
|
|
873
|
+
case "datetime", "date", "time", "duration":
|
|
874
|
+
return strings.ToLower(ref.local)
|
|
875
|
+
case "base64binary", "hexbinary":
|
|
876
|
+
return "bytes"
|
|
877
|
+
case "anytype", "anysimpletype":
|
|
878
|
+
return "any"
|
|
879
|
+
default:
|
|
880
|
+
return "string"
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return ref.local
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
func builtin(ref qname) bool {
|
|
887
|
+
return ref.local == "" || ref.ns == xsdNS || strings.Contains(strings.ToLower(ref.ns), "xmlschema")
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
func documentationOf(n *node) string {
|
|
891
|
+
if n == nil {
|
|
892
|
+
return ""
|
|
893
|
+
}
|
|
894
|
+
return n.documentation()
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
func uniqueContracts(in []Contract) []Contract {
|
|
898
|
+
seen := map[string]bool{}
|
|
899
|
+
var out []Contract
|
|
900
|
+
for _, contract := range in {
|
|
901
|
+
key := contract.Source + "\x00" + contract.Name
|
|
902
|
+
if !seen[key] {
|
|
903
|
+
out = append(out, contract)
|
|
904
|
+
seen[key] = true
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
return out
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
func uniqueFields(in []Field) []Field {
|
|
911
|
+
seen := map[string]bool{}
|
|
912
|
+
var out []Field
|
|
913
|
+
for _, field := range in {
|
|
914
|
+
if field.Name == "" || seen[field.Name] {
|
|
915
|
+
continue
|
|
916
|
+
}
|
|
917
|
+
seen[field.Name] = true
|
|
918
|
+
out = append(out, field)
|
|
919
|
+
}
|
|
920
|
+
return out
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
func qnameLess(left, right qname) bool {
|
|
924
|
+
if left.ns != right.ns {
|
|
925
|
+
return left.ns < right.ns
|
|
926
|
+
}
|
|
927
|
+
return left.local < right.local
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
func unique(in []string) []string {
|
|
931
|
+
seen := map[string]bool{}
|
|
932
|
+
var out []string
|
|
933
|
+
for _, value := range in {
|
|
934
|
+
if value != "" && !seen[value] {
|
|
935
|
+
seen[value] = true
|
|
936
|
+
out = append(out, value)
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return out
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
func first(values ...string) string {
|
|
943
|
+
for _, value := range values {
|
|
944
|
+
if strings.TrimSpace(value) != "" {
|
|
945
|
+
return strings.TrimSpace(value)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return ""
|
|
949
|
+
}
|