github.com/torresashjian/cli@v0.10.1-0.20210916231452-89080fe7069c/util/app.go (about) 1 package util 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "strings" 9 ) 10 11 // PartialAppDescriptor is the descriptor for a Flogo application 12 type PartialAppDescriptor struct { 13 AppModel string `json:"appModel"` 14 Imports []string `json:"imports"` 15 Triggers []interface{} `json:"triggers"` 16 Resources []interface{} `json:"resources"` 17 Actions []interface{} `json:"actions"` 18 } 19 20 type void struct{} 21 22 type AppImportDetails struct { 23 Imp Import 24 ContribDesc *FlogoContribDescriptor 25 TopLevel bool // a toplevel import i.e. from imports section 26 HasAliasRef bool // imports alias is used by a contrib reference 27 HasDirectRef bool // a direct reference exists for this import 28 } 29 30 func (d *AppImportDetails) Referenced() bool { 31 return d.HasAliasRef || d.HasDirectRef 32 } 33 34 func (d *AppImportDetails) IsCoreContrib() bool { 35 36 if d.ContribDesc == nil { 37 return false 38 } 39 ct := d.ContribDesc.GetContribType() 40 41 switch ct { 42 case "action", "trigger", "activity": 43 return true 44 default: 45 return false 46 } 47 } 48 49 type AppImports struct { 50 imports map[string]*AppImportDetails 51 orphanedRef map[string]void 52 53 resolveContribs bool 54 depManager DepManager 55 } 56 57 func (ai *AppImports) addImports(imports []string) error { 58 for _, anImport := range imports { 59 flogoImport, err := ParseImport(anImport) 60 if err != nil { 61 return err 62 } 63 64 if _, exists := ai.imports[flogoImport.GoImportPath()]; exists { 65 //todo warn about duplicate import? 66 continue 67 } 68 69 details, err := ai.newImportDetails(flogoImport) 70 if err != nil { 71 return err 72 } 73 details.TopLevel = true 74 75 ai.imports[flogoImport.GoImportPath()] = details 76 } 77 78 return nil 79 } 80 81 func (ai *AppImports) addReference(ref string, contribType string) error { 82 cleanedRef := strings.TrimSpace(ref) 83 84 if cleanedRef[0] == '#' { 85 if !ai.resolveContribs { 86 // wont be able to determine contribTypes for existing imports, so just return 87 return nil 88 } 89 90 alias := cleanedRef[1:] 91 found := false 92 for _, importDetails := range ai.imports { 93 94 if importDetails.TopLevel { 95 // alias refs can only be to toplevel imports 96 if importDetails.Imp.CanonicalAlias() == alias && importDetails.ContribDesc != nil && 97 importDetails.ContribDesc.GetContribType() == contribType { 98 importDetails.HasAliasRef = true 99 found = true 100 break 101 } 102 } 103 } 104 105 if !found { 106 ai.orphanedRef[cleanedRef] = void{} 107 } 108 109 } else { 110 flogoImport, err := ParseImport(ref) 111 if err != nil { 112 return err 113 } 114 115 if imp, exists := ai.imports[flogoImport.GoImportPath()]; exists { 116 if !imp.TopLevel { 117 //already accounted for 118 return nil 119 } 120 121 imp.HasDirectRef = true 122 return nil 123 } 124 125 //doesn't exists so add new import 126 details, err := ai.newImportDetails(flogoImport) 127 if err != nil { 128 return err 129 } 130 131 ai.imports[flogoImport.GoImportPath()] = details 132 } 133 134 return nil 135 } 136 137 func (ai *AppImports) newImportDetails(anImport Import) (*AppImportDetails, error) { 138 details := &AppImportDetails{Imp: anImport} 139 140 if ai.resolveContribs { 141 desc, err := GetContribDescriptorFromImport(ai.depManager, anImport) 142 if err != nil { 143 return nil, err 144 } 145 details.ContribDesc = desc 146 } 147 148 return details, nil 149 } 150 151 func (ai *AppImports) GetOrphanedReferences() []string { 152 var refs []string 153 for ref := range ai.orphanedRef { 154 refs = append(refs, ref) 155 } 156 157 return refs 158 } 159 160 func (ai *AppImports) GetAllImports() []Import { 161 var allImports []Import 162 for _, details := range ai.imports { 163 allImports = append(allImports, details.Imp) 164 } 165 166 return allImports 167 } 168 169 func (ai *AppImports) GetAllImportDetails() []*AppImportDetails { 170 171 var allImports []*AppImportDetails 172 for _, details := range ai.imports { 173 allImports = append(allImports, details) 174 } 175 176 return allImports 177 } 178 179 func GetAppImports(appJsonFile string, depManager DepManager, resolveContribs bool) (*AppImports, error) { 180 appJson, err := os.Open(appJsonFile) 181 if err != nil { 182 return nil, err 183 } 184 185 bytes, err := ioutil.ReadAll(appJson) 186 if err != nil { 187 return nil, err 188 } 189 190 appDesc := &PartialAppDescriptor{} 191 err = json.Unmarshal(bytes, appDesc) 192 if err != nil { 193 fmt.Fprintf(os.Stderr, "Unable to unmarshal flogo app json: %s", appJsonFile) 194 return nil, err 195 } 196 197 ai := &AppImports{depManager: depManager, resolveContribs: resolveContribs} 198 ai.imports = make(map[string]*AppImportDetails) 199 ai.orphanedRef = make(map[string]void) 200 201 err = ai.addImports(appDesc.Imports) 202 if err != nil { 203 return nil, err 204 } 205 206 err = extractAppReferences(ai, appDesc) 207 208 return ai, err 209 } 210 211 func extractAppReferences(ai *AppImports, appDesc *PartialAppDescriptor) error { 212 213 //triggers 214 for _, trg := range appDesc.Triggers { 215 if trgMap, ok := trg.(map[string]interface{}); ok { 216 217 // a ref should exists for every trigger 218 if refVal, ok := trgMap["ref"]; ok { 219 if strVal, ok := refVal.(string); ok { 220 err := ai.addReference(strVal, "trigger") 221 if err != nil { 222 return err 223 } 224 } 225 } 226 227 // actions are under handlers, so assume an action contribType 228 err := extractReferences(ai, trgMap["handlers"], "action") 229 if err != nil { 230 return err 231 } 232 } 233 } 234 235 //in actions section, refs should be to actions 236 err := extractReferences(ai, appDesc.Actions, "action") //action 237 if err != nil { 238 return err 239 } 240 241 //in resources section, refs should be to activities 242 err = extractReferences(ai, appDesc.Resources, "activity") //activity 243 if err != nil { 244 return err 245 } 246 247 return nil 248 } 249 250 func extractReferences(ai *AppImports, item interface{}, contribType string) error { 251 switch t := item.(type) { 252 case map[string]interface{}: 253 for key, val := range t { 254 if strVal, ok := val.(string); ok { 255 if key == "ref" { 256 err := ai.addReference(strVal, contribType) 257 if err != nil { 258 return err 259 } 260 } 261 } else { 262 err := extractReferences(ai, val, contribType) 263 if err != nil { 264 return err 265 } 266 } 267 } 268 case []interface{}: 269 for _, val := range t { 270 err := extractReferences(ai, val, contribType) 271 if err != nil { 272 return err 273 } 274 } 275 default: 276 } 277 278 return nil 279 }