/
definitions.go
228 lines (175 loc) · 5.91 KB
/
definitions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package main
import (
"github.com/jackmanlabs/errors"
"strings"
)
func deriveDefinitionsFromOperations(operationIntermediates []OperationIntermediate) (DefinitionStore, error) {
var defStore DefinitionStore = make(map[string]*DefinitionIntermediate)
// This first loop gets all the top-level definitions.
for _, operationIntermediate := range operationIntermediates {
for _, responseIntermediate := range operationIntermediate.Responses {
var typ SchemerDefiner = responseIntermediate.Type
referringPackage := operationIntermediate.PackagePath
goType := typ.GoType()
defs, err := getDefinition(defStore, referringPackage, goType)
if err != nil {
return defStore, errors.Stack(err)
}
defStore.Add(defs...)
if len(defs) > 0 {
typ.SetPackageName(defs[0].PackageName)
typ.SetPackagePath(defs[0].PackagePath)
}
}
for _, parameterIntermediate := range operationIntermediate.Parameters {
var typ SchemerDefiner = parameterIntermediate.Type
referringPackage := operationIntermediate.PackagePath
goType := typ.GoType()
defs, err := getDefinition(defStore, referringPackage, goType)
if err != nil {
return defStore, errors.Stack(err)
}
if len(defs) > 0 {
typ.SetPackageName(defs[0].PackageName)
typ.SetPackagePath(defs[0].PackagePath)
defStore.Add(defs...)
}
}
}
// This loop gets all the definitions of the sub-types of formerly defined
// definitions.
moreFound := true
for moreFound {
defs, err := findNextUnknownDefinition(defStore)
if err != nil {
return defStore, errors.Stack(err)
}
if len(defs) > 0 {
moreFound = true
defStore.Add(defs...)
} else {
moreFound = false
}
}
return defStore, nil
}
// This is used to allow incremental building of the definition store.
// Otherwise, we risk a lot of duplicate lookups.
func findNextUnknownDefinition(defStore DefinitionStore) ([]*DefinitionIntermediate, error) {
idx := 0
for _, def := range defStore {
for _, member := range def.Members {
//log.Printf("#%d Examining member: %s.%s (%s)", idx, def.Name, memberName, member.GoType())
var defs []*DefinitionIntermediate = make([]*DefinitionIntermediate, 0)
goTypes := getComponentTypes(member.GoType())
for _, goType := range goTypes {
if isPrimitive, _, _ := IsPrimitive(goType); isPrimitive {
continue
}
if _, ok := defStore.ExistsDefinition(def.PackagePath, goType); ok {
continue
}
// In the case of an embedded member, the package will be set internally.
referringPackage := def.PackagePath
if member.GetPackagePath() != "" {
referringPackage = member.GetPackagePath()
}
newDefs, err := getDefinition(defStore, referringPackage, goType)
if err != nil {
return nil, errors.Stack(err)
}
defs = append(defs, newDefs...)
}
if len(defs) > 0 {
return defs, nil
}
}
idx++
}
return nil, nil
}
func getComponentTypes(goType string) []string {
var types []string
// There was a real temptation to make this recursive instead of a loop.
if ok, t := IsSlice(goType); ok {
types = []string{t}
} else if ok, t, u := IsMap(goType); ok {
types = []string{t, u}
} else {
types = []string{goType}
}
return types
}
// This is troublesome.
// We have the definition store available, but I want to maintain a fairly
// functional coding style. If we do the add here, we can do the add
// intentionally, i.e. only when the add is necessary.
// On the other hand, I doubt a duplicate addition would would cost much.
// Or even happen frequently.
func getDefinition(defStore DefinitionStore, referringPackage, parentType string) ([]*DefinitionIntermediate, error) {
if referringPackage == "" {
return nil, errors.New("Referencing Package Path is empty.")
}
if parentType == "nil" {
return nil, nil
}
if isPrimitive, _, _ := IsPrimitive(parentType); isPrimitive {
return nil, nil
}
componentTypes := getComponentTypes(parentType)
var defs []*DefinitionIntermediate = make([]*DefinitionIntermediate, 0)
for _, goType := range componentTypes {
if isPrimitive, _, _ := IsPrimitive(goType); isPrimitive {
continue
}
def, ok := defStore.ExistsDefinition(referringPackage, goType)
if ok {
continue
}
def, err := findDefinition(referringPackage, goType)
if err != nil {
return defs, errors.Stack(err)
} else if def == nil {
return defs, errors.Newf("Failed to generate definition for type '%s' referenced in package '%s'", goType, referringPackage)
}
// Embedded types require special treatment. we need the definitions
// right now to construct the flattened struct. Also, we don't
// necessarily want the embedded struct type to show up in the
// definitions.
// Suggestion for enhancement: get the embedded types first, possibly in
// a separate store.
for _, embeddedType := range def.EmbeddedTypes {
embeddedDef, ok := defStore.ExistsDefinition(def.PackagePath, embeddedType)
if !ok {
embeddedDef, err = findDefinition(def.PackagePath, embeddedType)
if err != nil {
return nil, errors.Stack(err)
} else if embeddedDef == nil {
return defs, errors.Newf("Failed to generate definition for embedded type '%s' of '%s' referenced in package '%s'", embeddedType, goType, def.PackagePath)
}
}
mergeDefinitions(def, embeddedDef)
}
defs = append(defs, def)
}
return defs, nil
}
// What packages could have possibly contained this type?
func possibleImportPaths(pkgInfo PackageInfo, goType string) []string {
if !strings.Contains(goType, ".") {
return []string{pkgInfo.ImportPath}
}
chunks := strings.Split(goType, ".")
alias := chunks[0]
importPaths := make([]string, 0)
for importPath, aliases := range pkgInfo.Imports {
for _, alias_ := range aliases {
if alias_ == alias {
// I'm pretty sure that there should never be duplicate importPaths here.
// Otherwise, check for duplicates.
importPaths = append(importPaths, importPath)
}
}
}
return importPaths
}