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  }