github.com/vcilabs/webrpc@v0.5.2-0.20201116131534-162e27b1b33b/schema/message.go (about) 1 package schema 2 3 import ( 4 "strings" 5 6 "github.com/pkg/errors" 7 ) 8 9 type Message struct { 10 Name VarName `json:"name"` 11 Type MessageType `json:"type"` 12 Fields []*MessageField `json:"fields"` 13 14 // EnumType determined for enum types during parsing time 15 EnumType *VarType `json:"-"` 16 } 17 18 type MessageType string // "enum" | "struct" 19 20 type MessageField struct { 21 Name VarName `json:"name"` 22 Type *VarType `json:"type"` 23 24 Optional bool `json:"optional"` 25 Value string `json:"value"` // used by enums 26 27 // Meta store extra metadata on a field for plugins 28 Meta []MessageFieldMeta `json:"meta"` 29 } 30 31 type MessageFieldMeta map[string]interface{} 32 33 func (m *Message) Parse(schema *WebRPCSchema) error { 34 // Message name 35 msgName := string(m.Name) 36 if msgName == "" { 37 return errors.Errorf("schema error: message name cannot be empty") 38 } 39 40 // Ensure we don't have dupe message types (w/ normalization) 41 name := strings.ToLower(msgName) 42 for _, msg := range schema.Messages { 43 if msg != m && name == strings.ToLower(string(msg.Name)) { 44 return errors.Errorf("schema error: duplicate message type detected, '%s'", msgName) 45 } 46 } 47 48 // Ensure we have a message type 49 if string(m.Type) != "enum" && string(m.Type) != "struct" { 50 return errors.Errorf("schema error: message type must be 'enum' or 'struct' for '%s'", msgName) 51 } 52 53 // NOTE: so far, lets allow messages with no fields.. so just empty object, why, I dunno, but gRPC allows it 54 // Ensure we have some fields 55 // if len(m.Fields) == 0 { 56 // return errors.Errorf("schema error: message type must contain at least one field for '%s'", msgName) 57 // } 58 59 // Verify field names and ensure we don't have any duplicate field names 60 fieldList := map[string]string{} 61 for _, field := range m.Fields { 62 if string(field.Name) == "" { 63 return errors.Errorf("schema error: detected empty field name in message '%s", msgName) 64 } 65 66 fieldName := string(field.Name) 67 nFieldName := strings.ToLower(fieldName) 68 69 // Verify name format 70 if !IsValidArgName(fieldName) { 71 return errors.Errorf("schema error: invalid field name of '%s' in message '%s'", fieldName, msgName) 72 } 73 74 // Ensure no dupes 75 if _, ok := fieldList[nFieldName]; ok { 76 return errors.Errorf("schema error: detected duplicate field name of '%s' in message '%s'", fieldName, msgName) 77 } 78 fieldList[nFieldName] = fieldName 79 } 80 81 // Parse+validate message fields 82 for _, field := range m.Fields { 83 err := field.Type.Parse(schema) 84 if err != nil { 85 return err 86 } 87 } 88 89 // For enums only, ensure all field types are the same 90 if m.Type == "enum" { 91 // ensure enum fields have value key set, and are all of the same type 92 fieldTypes := map[string]struct{}{} 93 for _, field := range m.Fields { 94 fieldType := field.Type.expr 95 fieldTypes[fieldType] = struct{}{} 96 if field.Value == "" { 97 return errors.Errorf("schema error: enum message '%s' with field '%s' is missing value", m.Name, field.Name) 98 } 99 } 100 if len(fieldTypes) > 1 { 101 return errors.Errorf("schema error: enum message '%s' must all have the same field type", m.Name) 102 } 103 104 // ensure enum type is one of the allowed types.. aka integer 105 fieldType := m.Fields[0].Type 106 if !isValidVarType(fieldType.String(), VarIntegerDataTypes) { 107 return errors.Errorf("schema error: enum message '%s' field '%s' is invalid. must be an integer type.", m.Name, fieldType.String()) 108 } 109 m.EnumType = fieldType 110 } 111 112 // For structs only 113 if m.Type == "struct" { 114 for _, field := range m.Fields { 115 if field.Value != "" { 116 return errors.Errorf("schema error: struct message '%s' with field '%s' cannot contain value field - please remove it", m.Name, field.Name) 117 } 118 } 119 120 } 121 122 return nil 123 }