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 }