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  }