github.com/vcilabs/webrpc@v0.5.2-0.20201116131534-162e27b1b33b/schema/ridl/ridl.go (about)

     1  package ridl
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strconv"
     8  
     9  	"github.com/webrpc/webrpc/schema"
    10  )
    11  
    12  var (
    13  	schemaMessageTypeEnum   = schema.MessageType("enum")
    14  	schemaMessageTypeStruct = schema.MessageType("struct")
    15  )
    16  
    17  type Parser struct {
    18  	parent  *Parser
    19  	imports map[string]struct{}
    20  
    21  	reader *schema.Reader
    22  }
    23  
    24  func NewParser(r *schema.Reader) *Parser {
    25  	return &Parser{
    26  		reader: r,
    27  		imports: map[string]struct{}{
    28  			// this file imports itself
    29  			r.File: struct{}{},
    30  		},
    31  	}
    32  }
    33  
    34  func (p *Parser) Parse() (*schema.WebRPCSchema, error) {
    35  	s, err := p.parse()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  
    40  	// run through schema validator, last step to ensure all is good.
    41  	err = s.Validate()
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return s, nil
    47  }
    48  
    49  func (p *Parser) importRIDLFile(path string) (*schema.WebRPCSchema, error) {
    50  	if mockImport {
    51  		return &schema.WebRPCSchema{}, nil
    52  	}
    53  
    54  	for node := p; node != nil; node = node.parent {
    55  		if _, imported := node.imports[path]; imported {
    56  			return nil, fmt.Errorf("circular import %q in file %q", filepath.Base(path), p.reader.File)
    57  		}
    58  		node.imports[path] = struct{}{}
    59  	}
    60  
    61  	fp, err := os.Open(path)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	defer fp.Close()
    66  
    67  	m := NewParser(schema.NewReader(fp, path))
    68  	m.parent = p
    69  	return m.Parse()
    70  }
    71  
    72  func (p *Parser) parse() (*schema.WebRPCSchema, error) {
    73  	q, err := newParser(p.reader)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	if err = q.run(); err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	s := &schema.WebRPCSchema{
    83  		Imports:  []*schema.Import{},
    84  		Messages: []*schema.Message{},
    85  		Services: []*schema.Service{},
    86  	}
    87  
    88  	// main definitions
    89  	for _, line := range q.root.Definitions() {
    90  		key, value := line.Left().String(), line.Right().String()
    91  
    92  		switch key {
    93  		case wordWebRPC:
    94  			if s.WebRPCVersion != "" {
    95  				return nil, fmt.Errorf(`webrpc was previously declared`)
    96  			}
    97  			s.WebRPCVersion = value
    98  		case wordName:
    99  			if s.Name != "" {
   100  				return nil, fmt.Errorf(`name was previously declared`)
   101  			}
   102  			s.Name = value
   103  		case wordVersion:
   104  			if s.SchemaVersion != "" {
   105  				return nil, fmt.Errorf(`version was previously declared`)
   106  			}
   107  			s.SchemaVersion = value
   108  		default:
   109  			return nil, fmt.Errorf("unknown definition %q", key)
   110  		}
   111  	}
   112  
   113  	// imports
   114  	for _, line := range q.root.Imports() {
   115  		importPath := filepath.Join(filepath.Dir(p.reader.File), line.Path().String())
   116  
   117  		importDef := &schema.Import{
   118  			Path:    importPath,
   119  			Members: []string{},
   120  		}
   121  		for _, member := range line.Members() {
   122  			importDef.Members = append(importDef.Members, member.String())
   123  		}
   124  
   125  		imported, err := p.importRIDLFile(importDef.Path)
   126  		if err != nil {
   127  			return nil, p.trace(err, line.Path())
   128  		}
   129  
   130  		for i := range imported.Messages {
   131  			if isImportAllowed(string(imported.Messages[i].Name), importDef.Members) {
   132  				s.Messages = append(s.Messages, imported.Messages[i])
   133  			}
   134  		}
   135  		for i := range imported.Services {
   136  			if isImportAllowed(string(imported.Services[i].Name), importDef.Members) {
   137  				s.Services = append(s.Services, imported.Services[i])
   138  			}
   139  		}
   140  
   141  		s.Imports = append(s.Imports, importDef)
   142  	}
   143  
   144  	// pushing enums (1st pass)
   145  	for _, line := range q.root.Enums() {
   146  		s.Messages = append(s.Messages, &schema.Message{
   147  			Name:   schema.VarName(line.Name().String()),
   148  			Type:   schemaMessageTypeEnum,
   149  			Fields: []*schema.MessageField{},
   150  		})
   151  	}
   152  
   153  	// pushing messages (1st pass)
   154  	for _, line := range q.root.Messages() {
   155  		s.Messages = append(s.Messages, &schema.Message{
   156  			Name: schema.VarName(line.Name().String()),
   157  			Type: schemaMessageTypeStruct,
   158  		})
   159  	}
   160  
   161  	// pushing services (1st pass)
   162  	for _, service := range q.root.Services() {
   163  		// push service
   164  		s.Services = append(s.Services, &schema.Service{
   165  			Name: schema.VarName(service.Name().String()),
   166  		})
   167  	}
   168  
   169  	// enum fields
   170  	for _, line := range q.root.Enums() {
   171  		name := schema.VarName(line.Name().String())
   172  		enumDef := s.GetMessageByName(string(name))
   173  
   174  		if enumDef == nil {
   175  			return nil, fmt.Errorf("unexpected error, could not find definition for: %v", name)
   176  		}
   177  
   178  		var enumType schema.VarType
   179  		err := schema.ParseVarTypeExpr(s, line.TypeName().String(), &enumType)
   180  		if err != nil {
   181  			return nil, fmt.Errorf("unknown data type: %v", line.TypeName())
   182  		}
   183  
   184  		for i, def := range line.Values() {
   185  			key, val := def.Left().String(), def.Right().String()
   186  
   187  			if val == "" {
   188  				val = strconv.Itoa(i)
   189  			}
   190  
   191  			enumDef.Fields = append(enumDef.Fields, &schema.MessageField{
   192  				Name:  schema.VarName(key),
   193  				Type:  &enumType,
   194  				Value: val,
   195  			})
   196  		}
   197  
   198  		enumDef.EnumType = &enumType
   199  	}
   200  
   201  	// message fields
   202  	for _, line := range q.root.Messages() {
   203  		name := schema.VarName(line.Name().String())
   204  		messageDef := s.GetMessageByName(string(name))
   205  
   206  		if messageDef == nil {
   207  			return nil, fmt.Errorf("unexpected error, could not find definition for: %v", name)
   208  		}
   209  
   210  		for _, def := range line.Fields() {
   211  			fieldName, fieldType := def.Left().String(), def.Right().String()
   212  
   213  			var varType schema.VarType
   214  			err := schema.ParseVarTypeExpr(s, fieldType, &varType)
   215  			if err != nil {
   216  				return nil, fmt.Errorf("unknown data type: %v", fieldType)
   217  			}
   218  
   219  			field := &schema.MessageField{
   220  				Name:     schema.VarName(fieldName),
   221  				Optional: def.Optional(),
   222  				Type:     &varType,
   223  			}
   224  			for _, meta := range def.Meta() {
   225  				key, val := meta.Left().String(), meta.Right().String()
   226  				field.Meta = append(field.Meta, schema.MessageFieldMeta{
   227  					key: val,
   228  				})
   229  			}
   230  			messageDef.Fields = append(messageDef.Fields, field)
   231  		}
   232  	}
   233  
   234  	// Services
   235  	for _, service := range q.root.Services() {
   236  		methods := []*schema.Method{}
   237  
   238  		for _, method := range service.Methods() {
   239  
   240  			inputs, err := buildArgumentsList(s, method.Inputs())
   241  			if err != nil {
   242  				return nil, err
   243  			}
   244  
   245  			outputs, err := buildArgumentsList(s, method.Outputs())
   246  			if err != nil {
   247  				return nil, err
   248  			}
   249  
   250  			// push method
   251  			methods = append(methods, &schema.Method{
   252  				Name:         schema.VarName(method.Name().String()),
   253  				StreamInput:  method.StreamInput(),
   254  				StreamOutput: method.StreamOutput(),
   255  				Inputs:       inputs,
   256  				Outputs:      outputs,
   257  			})
   258  		}
   259  
   260  		serviceDef := s.GetServiceByName(service.Name().String())
   261  		serviceDef.Methods = methods
   262  	}
   263  
   264  	return s, nil
   265  }
   266  
   267  func (p *Parser) trace(err error, tok *TokenNode) error {
   268  	return fmt.Errorf(
   269  		"%v\nnear string %q\n\tfrom %v:%d:%d",
   270  		err,
   271  		tok.tok.val,
   272  		p.reader.File,
   273  		tok.tok.line,
   274  		tok.tok.col,
   275  	)
   276  }
   277  
   278  func isImportAllowed(name string, whitelist []string) bool {
   279  	if len(whitelist) < 1 {
   280  		return true
   281  	}
   282  	for i := range whitelist {
   283  		if name == whitelist[i] {
   284  			return true
   285  		}
   286  	}
   287  	return false
   288  }
   289  
   290  func buildArgumentsList(s *schema.WebRPCSchema, args []*ArgumentNode) ([]*schema.MethodArgument, error) {
   291  	output := []*schema.MethodArgument{}
   292  
   293  	for _, arg := range args {
   294  
   295  		var varType schema.VarType
   296  		err := schema.ParseVarTypeExpr(s, arg.TypeName().String(), &varType)
   297  		if err != nil {
   298  			return nil, fmt.Errorf("unknown data type: %v", arg.TypeName().String())
   299  		}
   300  
   301  		methodArgument := &schema.MethodArgument{
   302  			Name:     schema.VarName(arg.Name().String()),
   303  			Type:     &varType,
   304  			Optional: arg.Optional(),
   305  		}
   306  
   307  		output = append(output, methodArgument)
   308  	}
   309  
   310  	return output, nil
   311  }