...

Source file src/github.com/udacity/graphb/field.go

Documentation: github.com/udacity/graphb

     1  package graphb
     2  
     3  import (
     4  	"github.com/pkg/errors"
     5  )
     6  
     7  // Field is a recursive data struct which represents a GraphQL query field.
     8  type Field struct {
     9  	Name      string
    10  	Alias     string
    11  	Arguments []Argument
    12  	Fields    []*Field
    13  	E         error
    14  }
    15  
    16  // Implement fieldContainer
    17  func (f *Field) getFields() []*Field {
    18  	return f.Fields
    19  }
    20  
    21  func (f *Field) setFields(fs []*Field) {
    22  	f.Fields = fs
    23  }
    24  
    25  // StringChan returns read only string token channel or an error.
    26  // It checks if there is a circle.
    27  func (f *Field) StringChan() (<-chan string, error) {
    28  	// todo: new style error handling
    29  	if err := f.check(); err != nil {
    30  		// return a closed channel instead of nil for receiving from nil blocks forever, hard to debug and confusing to users.
    31  		ch := make(chan string)
    32  		close(ch)
    33  		return ch, errors.WithStack(err)
    34  	}
    35  	return f.stringChan(), nil
    36  }
    37  
    38  // One may have noticed that there is a public StringChan and a private stringChan.
    39  // The different being the public method checks the validity of the Field structure
    40  // while the private counterpart assumes the validity.
    41  func (f *Field) stringChan() <-chan string {
    42  
    43  	tokenChan := make(chan string)
    44  
    45  	go func() {
    46  		// emit alias and names
    47  		if f.Alias != "" {
    48  			tokenChan <- f.Alias
    49  			tokenChan <- tokenColumn
    50  		}
    51  		tokenChan <- f.Name
    52  
    53  		// emit argument tokens
    54  		if len(f.Arguments) > 0 {
    55  			tokenChan <- tokenLP
    56  			for i := range f.Arguments {
    57  				if i != 0 {
    58  					tokenChan <- tokenComma
    59  				}
    60  				for str := range f.Arguments[i].stringChan() {
    61  					tokenChan <- str
    62  				}
    63  			}
    64  			tokenChan <- tokenRP
    65  		}
    66  
    67  		// emit field tokens
    68  		if len(f.Fields) > 0 {
    69  			tokenChan <- tokenLB
    70  			for i, field := range f.Fields {
    71  				if field != nil {
    72  					if i != 0 {
    73  						tokenChan <- tokenComma
    74  					}
    75  					for str := range field.stringChan() {
    76  						tokenChan <- str
    77  					}
    78  				}
    79  			}
    80  			tokenChan <- tokenRB
    81  		}
    82  		close(tokenChan)
    83  	}()
    84  	return tokenChan
    85  }
    86  
    87  func (f *Field) check() error {
    88  	if err := f.checkCycle(); err != nil {
    89  		return errors.WithStack(err)
    90  	}
    91  	if err := f.checkOther(); err != nil {
    92  		return errors.WithStack(err)
    93  	}
    94  	return nil
    95  }
    96  
    97  // checkOther checks the validity of this Field and returns nil on valid Field.
    98  func (f *Field) checkOther() error {
    99  	// Check validity of names
   100  	if !validName.MatchString(f.Name) {
   101  		return errors.WithStack(InvalidNameErr{fieldName, f.Name})
   102  	}
   103  	if err := f.checkAlias(); err != nil {
   104  		return errors.WithStack(err)
   105  	}
   106  	for _, arg := range f.Arguments {
   107  		if !validName.MatchString(arg.Name) {
   108  			return errors.WithStack(InvalidNameErr{argumentName, arg.Name})
   109  		}
   110  	}
   111  
   112  	// Check sub fields
   113  	for _, subF := range f.Fields {
   114  		if err := subF.checkOther(); err != nil {
   115  			return errors.WithStack(err)
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  func (f *Field) checkAlias() error {
   122  	if f.Alias != "" && !validName.MatchString(f.Alias) {
   123  		return errors.WithStack(InvalidNameErr{aliasName, f.Alias})
   124  	}
   125  	return nil
   126  }
   127  
   128  // todo: reports the cycle path
   129  func (f *Field) checkCycle() error {
   130  	if err := reach(f, f); err != nil {
   131  		return errors.WithStack(err)
   132  	}
   133  	return nil
   134  }
   135  
   136  ////////////////
   137  // Public API //
   138  ////////////////
   139  
   140  // MakeField constructs a Field of given name and return the pointer to this Field.
   141  func MakeField(name string) *Field {
   142  	return &Field{Name: name}
   143  }
   144  
   145  // SetArguments sets the arguments of a Field and return the pointer to this Field.
   146  func (f *Field) SetArguments(arguments ...Argument) *Field {
   147  	f.Arguments = arguments
   148  	return f
   149  }
   150  
   151  func (f *Field) AddArguments(argument ...Argument) *Field {
   152  	f.Arguments = append(f.Arguments, argument...)
   153  	return f
   154  }
   155  
   156  // SetFields sets the sub fields of a Field and return the pointer to this Field.
   157  func (f *Field) SetFields(fs ...*Field) *Field {
   158  	f.Fields = fs
   159  	return f
   160  }
   161  
   162  // SetAlias sets the alias of a Field and return the pointer to this Field.
   163  func (f *Field) SetAlias(alias string) *Field {
   164  	f.Alias = alias
   165  	return f
   166  }
   167  
   168  /////////////
   169  // Helpers //
   170  /////////////
   171  // reach checks if f1 can be reached by f2 either directly (itself) or indirectly (children)
   172  func reach(f1, f2 *Field) error {
   173  	if f1 == nil || f2 == nil {
   174  		return errors.WithStack(NilFieldErr{})
   175  	}
   176  	for _, field := range f2.Fields {
   177  		if f1 == field {
   178  			return errors.WithStack(CyclicFieldErr{*f1})
   179  		}
   180  		if err := reach(f1, field); err != nil {
   181  			return errors.WithStack(err)
   182  		}
   183  	}
   184  	return nil
   185  }
   186  

View as plain text