github.com/shogo82148/goa-v1@v1.6.2/dslengine/runner.go (about)

     1  package dslengine
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"runtime"
     9  	"strings"
    10  )
    11  
    12  var (
    13  	// Errors contains the DSL execution errors if any.
    14  	Errors MultiError
    15  
    16  	// Global DSL evaluation stack
    17  	ctxStack contextStack
    18  
    19  	// Registered DSL roots
    20  	roots []Root
    21  
    22  	// DSL package paths used to compute error locations (skip the frames in these packages)
    23  	dslPackages map[string]bool
    24  )
    25  
    26  type (
    27  	// Error represents an error that occurred while running the API DSL.
    28  	// It contains the name of the file and line number of where the error
    29  	// occurred as well as the original Go error.
    30  	Error struct {
    31  		GoError error
    32  		File    string
    33  		Line    int
    34  	}
    35  
    36  	// MultiError collects all DSL errors. It implements error.
    37  	MultiError []*Error
    38  
    39  	// DSL evaluation contexts stack
    40  	contextStack []Definition
    41  )
    42  
    43  func init() {
    44  	dslPackages = map[string]bool{
    45  		"github.com/shogo82148/goa-v1/":            true,
    46  		"github.com/shogo82148/goa-v1/middleware/": true,
    47  		"github.com/shogo82148/goa-v1/encoding/":   true,
    48  		"github.com/shogo82148/goa-v1/logging/":    true,
    49  	}
    50  }
    51  
    52  // Register adds a DSL Root to be executed by Run.
    53  func Register(r Root) {
    54  	for _, o := range roots {
    55  		if r.DSLName() == o.DSLName() {
    56  			fmt.Fprintf(os.Stderr, "goagen: duplicate DSL %s", r.DSLName())
    57  			os.Exit(1)
    58  		}
    59  	}
    60  	t := reflect.TypeOf(r)
    61  	if t.Kind() == reflect.Ptr {
    62  		t = t.Elem()
    63  	}
    64  	dslPackages[t.PkgPath()] = true
    65  	roots = append(roots, r)
    66  }
    67  
    68  // Reset uses the registered RootFuncs to re-initialize the DSL roots.
    69  // This is useful to tests.
    70  func Reset() {
    71  	for _, r := range roots {
    72  		r.Reset()
    73  	}
    74  	Errors = nil
    75  }
    76  
    77  // Run runs the given root definitions. It iterates over the definition sets
    78  // multiple times to first execute the DSL, the validate the resulting
    79  // definitions and finally finalize them. The executed DSL may register new
    80  // roots to have them be executed (last) in the same run.
    81  func Run() error {
    82  	if len(roots) == 0 {
    83  		return nil
    84  	}
    85  	roots, err := SortRoots()
    86  	if err != nil {
    87  		return err
    88  	}
    89  	Errors = nil
    90  	executed := 0
    91  	recursed := 0
    92  	for executed < len(roots) {
    93  		recursed++
    94  		start := executed
    95  		executed = len(roots)
    96  		for _, root := range roots[start:] {
    97  			root.IterateSets(runSet)
    98  		}
    99  		if recursed > 100 {
   100  			// Let's cross that bridge once we get there
   101  			return fmt.Errorf("too many generated roots, infinite loop?")
   102  		}
   103  	}
   104  	if Errors != nil {
   105  		return Errors
   106  	}
   107  	for _, root := range roots {
   108  		root.IterateSets(validateSet)
   109  	}
   110  	if Errors != nil {
   111  		return Errors
   112  	}
   113  	for _, root := range roots {
   114  		root.IterateSets(finalizeSet)
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // Execute runs the given DSL to initialize the given definition. It returns true on success.
   121  // It returns false and appends to Errors on failure.
   122  // Note that `Run` takes care of calling `Execute` on all definitions that implement Source.
   123  // This function is intended for use by definitions that run the DSL at declaration time rather than
   124  // store the DSL for execution by the dsl engine (usually simple independent definitions).
   125  // The DSL should use ReportError to record DSL execution errors.
   126  func Execute(dsl func(), def Definition) bool {
   127  	if dsl == nil {
   128  		return true
   129  	}
   130  	initCount := len(Errors)
   131  	ctxStack = append(ctxStack, def)
   132  	dsl()
   133  	ctxStack = ctxStack[:len(ctxStack)-1]
   134  	return len(Errors) <= initCount
   135  }
   136  
   137  // CurrentDefinition returns the definition whose initialization DSL is currently being executed.
   138  func CurrentDefinition() Definition {
   139  	current := ctxStack.Current()
   140  	if current == nil {
   141  		return &TopLevelDefinition{}
   142  	}
   143  	return current
   144  }
   145  
   146  // IsTopLevelDefinition returns true if the currently evaluated DSL is a root
   147  // DSL (i.e. is not being run in the context of another definition).
   148  func IsTopLevelDefinition() bool {
   149  	_, ok := CurrentDefinition().(*TopLevelDefinition)
   150  	return ok
   151  }
   152  
   153  // TopLevelDefinition represents the top-level file definitions, done
   154  // with `var _ = `.  An instance of this object is returned by
   155  // `CurrentDefinition()` when at the top-level.
   156  type TopLevelDefinition struct{}
   157  
   158  // Context tells the DSL engine which context we're in when showing
   159  // errors.
   160  func (t *TopLevelDefinition) Context() string { return "top-level" }
   161  
   162  // ReportError records a DSL error for reporting post DSL execution.
   163  func ReportError(fm string, vals ...interface{}) {
   164  	var suffix string
   165  	if cur := ctxStack.Current(); cur != nil {
   166  		if ctx := cur.Context(); ctx != "" {
   167  			suffix = fmt.Sprintf(" in %s", ctx)
   168  		}
   169  	} else {
   170  		suffix = " (top level)"
   171  	}
   172  	err := fmt.Errorf(fm+suffix, vals...)
   173  	file, line := computeErrorLocation()
   174  	Errors = append(Errors, &Error{
   175  		GoError: err,
   176  		File:    file,
   177  		Line:    line,
   178  	})
   179  }
   180  
   181  // FailOnError will exit with code 1 if `err != nil`. This function
   182  // will handle properly the MultiError this dslengine provides.
   183  func FailOnError(err error) {
   184  	if merr, ok := err.(MultiError); ok {
   185  		if len(merr) == 0 {
   186  			return
   187  		}
   188  		fmt.Fprint(os.Stderr, merr.Error())
   189  		os.Exit(1)
   190  	}
   191  	if err != nil {
   192  		fmt.Fprint(os.Stderr, err.Error())
   193  		os.Exit(1)
   194  	}
   195  }
   196  
   197  // PrintFilesOrFail will print the file list. Use it with a
   198  // generator's `Generate()` function to output the generated list of
   199  // files or quit on error.
   200  func PrintFilesOrFail(files []string, err error) {
   201  	FailOnError(err)
   202  	fmt.Println(strings.Join(files, "\n"))
   203  }
   204  
   205  // IncompatibleDSL should be called by DSL functions when they are
   206  // invoked in an incorrect context (e.g. "Params" in "Resource").
   207  func IncompatibleDSL() {
   208  	elems := strings.Split(caller(), ".")
   209  	ReportError("invalid use of %s", elems[len(elems)-1])
   210  }
   211  
   212  // InvalidArgError records an invalid argument error.
   213  // It is used by DSL functions that take dynamic arguments.
   214  func InvalidArgError(expected string, actual interface{}) {
   215  	ReportError("cannot use %#v (type %s) as type %s",
   216  		actual, reflect.TypeOf(actual), expected)
   217  }
   218  
   219  // Error returns the error message.
   220  func (m MultiError) Error() string {
   221  	msgs := make([]string, len(m))
   222  	for i, de := range m {
   223  		msgs[i] = de.Error()
   224  	}
   225  	return strings.Join(msgs, "\n")
   226  }
   227  
   228  // Error returns the underlying error message.
   229  func (de *Error) Error() string {
   230  	if err := de.GoError; err != nil {
   231  		if de.File == "" {
   232  			return err.Error()
   233  		}
   234  		return fmt.Sprintf("[%s:%d] %s", de.File, de.Line, err.Error())
   235  	}
   236  	return ""
   237  }
   238  
   239  // Current evaluation context, i.e. object being currently built by DSL
   240  func (s contextStack) Current() Definition {
   241  	if len(s) == 0 {
   242  		return nil
   243  	}
   244  	return s[len(s)-1]
   245  }
   246  
   247  // computeErrorLocation implements a heuristic to find the location in the user
   248  // code where the error occurred. It walks back the callstack until the file
   249  // doesn't match "/goa/design/*.go" or one of the DSL package paths.
   250  // When successful it returns the file name and line number, empty string and
   251  // 0 otherwise.
   252  func computeErrorLocation() (file string, line int) {
   253  	skipFunc := func(file string) bool {
   254  		if strings.HasSuffix(file, "_test.go") { // Be nice with tests
   255  			return false
   256  		}
   257  		file = filepath.ToSlash(file)
   258  		for pkg := range dslPackages {
   259  			if strings.Contains(file, pkg) {
   260  				return true
   261  			}
   262  		}
   263  		return false
   264  	}
   265  	depth := 2
   266  	_, file, line, _ = runtime.Caller(depth)
   267  	for skipFunc(file) {
   268  		depth++
   269  		_, file, line, _ = runtime.Caller(depth)
   270  	}
   271  	wd, err := os.Getwd()
   272  	if err != nil {
   273  		return
   274  	}
   275  	wd, err = filepath.Abs(wd)
   276  	if err != nil {
   277  		return
   278  	}
   279  	f, err := filepath.Rel(wd, file)
   280  	if err != nil {
   281  		return
   282  	}
   283  	file = f
   284  	return
   285  }
   286  
   287  // runSet executes the DSL for all definitions in the given set. The definition DSLs may append to
   288  // the set as they execute.
   289  func runSet(set DefinitionSet) error {
   290  	executed := 0
   291  	recursed := 0
   292  	for executed < len(set) {
   293  		recursed++
   294  		for _, def := range set[executed:] {
   295  			executed++
   296  			if source, ok := def.(Source); ok {
   297  				if dsl := source.DSL(); dsl != nil {
   298  					Execute(dsl, source)
   299  				}
   300  			}
   301  		}
   302  		if recursed > 100 {
   303  			return fmt.Errorf("too many generated definitions, infinite loop?")
   304  		}
   305  	}
   306  	return nil
   307  }
   308  
   309  // validateSet runs the validation on all the set definitions that define one.
   310  func validateSet(set DefinitionSet) error {
   311  	errors := &ValidationErrors{}
   312  	for _, def := range set {
   313  		if validate, ok := def.(Validate); ok {
   314  			if err := validate.Validate(); err != nil {
   315  				errors.AddError(def, err)
   316  			}
   317  		}
   318  	}
   319  	err := errors.AsError()
   320  	if err != nil {
   321  		Errors = append(Errors, &Error{GoError: err})
   322  	}
   323  	return err
   324  }
   325  
   326  // finalizeSet runs the validation on all the set definitions that define one.
   327  func finalizeSet(set DefinitionSet) error {
   328  	for _, def := range set {
   329  		if finalize, ok := def.(Finalize); ok {
   330  			finalize.Finalize()
   331  		}
   332  	}
   333  	return nil
   334  }
   335  
   336  // SortRoots orders the DSL roots making sure dependencies are last. It returns an error if there
   337  // is a dependency cycle.
   338  func SortRoots() ([]Root, error) {
   339  	if len(roots) == 0 {
   340  		return nil, nil
   341  	}
   342  	// First flatten dependencies for each root
   343  	rootDeps := make(map[string][]Root, len(roots))
   344  	rootByName := make(map[string]Root, len(roots))
   345  	for _, r := range roots {
   346  		sorted := sortDependencies(r, func(r Root) []Root { return r.DependsOn() })
   347  		length := len(sorted)
   348  		for i := 0; i < length/2; i++ {
   349  			sorted[i], sorted[length-i-1] = sorted[length-i-1], sorted[i]
   350  		}
   351  		rootDeps[r.DSLName()] = sorted
   352  		rootByName[r.DSLName()] = r
   353  	}
   354  	// Now check for cycles
   355  	for name, deps := range rootDeps {
   356  		root := rootByName[name]
   357  		for otherName, otherdeps := range rootDeps {
   358  			other := rootByName[otherName]
   359  			if root.DSLName() == other.DSLName() {
   360  				continue
   361  			}
   362  			dependsOnOther := false
   363  			for _, dep := range deps {
   364  				if dep.DSLName() == other.DSLName() {
   365  					dependsOnOther = true
   366  					break
   367  				}
   368  			}
   369  			if dependsOnOther {
   370  				for _, dep := range otherdeps {
   371  					if dep.DSLName() == root.DSLName() {
   372  						return nil, fmt.Errorf("dependency cycle: %s and %s depend on each other (directly or not)",
   373  							root.DSLName(), other.DSLName())
   374  					}
   375  				}
   376  			}
   377  		}
   378  	}
   379  	// Now sort top level DSLs
   380  	var sorted []Root
   381  	for _, r := range roots {
   382  		s := sortDependencies(r, func(r Root) []Root { return rootDeps[r.DSLName()] })
   383  		for _, s := range s {
   384  			found := false
   385  			for _, r := range sorted {
   386  				if r.DSLName() == s.DSLName() {
   387  					found = true
   388  					break
   389  				}
   390  			}
   391  			if !found {
   392  				sorted = append(sorted, s)
   393  			}
   394  		}
   395  	}
   396  	return sorted, nil
   397  }
   398  
   399  // sortDependencies sorts the depencies of the given root in the given slice.
   400  func sortDependencies(root Root, depFunc func(Root) []Root) []Root {
   401  	seen := make(map[string]bool, len(roots))
   402  	var sorted []Root
   403  	sortDependenciesR(root, seen, &sorted, depFunc)
   404  	return sorted
   405  }
   406  
   407  // sortDependenciesR sorts the depencies of the given root in the given slice.
   408  func sortDependenciesR(root Root, seen map[string]bool, sorted *[]Root, depFunc func(Root) []Root) {
   409  	for _, dep := range depFunc(root) {
   410  		if !seen[dep.DSLName()] {
   411  			seen[root.DSLName()] = true
   412  			sortDependenciesR(dep, seen, sorted, depFunc)
   413  		}
   414  	}
   415  	*sorted = append(*sorted, root)
   416  }
   417  
   418  // caller returns the name of calling function.
   419  func caller() string {
   420  	pc, file, _, ok := runtime.Caller(2)
   421  	if ok && filepath.Base(file) == "current.go" {
   422  		pc, _, _, ok = runtime.Caller(3)
   423  	}
   424  	if !ok {
   425  		return "<unknown>"
   426  	}
   427  
   428  	return runtime.FuncForPC(pc).Name()
   429  }