package ordering import ( "fmt" "strconv" "strings" "unicode" "go.einride.tech/aip/fieldmask" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/fieldmaskpb" ) // OrderBy represents an ordering directive. type OrderBy struct { // Fields are the fields to order by. Fields []Field } // Field represents a single ordering field. type Field struct { // Path is the path of the field, including subfields. Path string // Desc indicates if the ordering of the field is descending. Desc bool } // SubFields returns the individual subfields of the field path, including the top-level subfield. // // Subfields are specified with a . character, such as foo.bar or address.street. func (f Field) SubFields() []string { if f.Path == "" { return nil } return strings.Split(f.Path, ".") } // UnmarshalString sets o from the provided ordering string. . func (o *OrderBy) UnmarshalString(s string) error { o.Fields = o.Fields[:0] if s == "" { // fast path for no ordering return nil } for _, r := range s { if !unicode.IsLetter(r) && !unicode.IsNumber(r) && r != '_' && r != ' ' && r != ',' && r != '.' { return fmt.Errorf("unmarshal order by '%s': invalid character %s", s, strconv.QuoteRune(r)) } } fields := strings.Split(s, ",") o.Fields = make([]Field, 0, len(fields)) for _, field := range fields { parts := strings.Fields(field) switch len(parts) { case 1: // default ordering (ascending) o.Fields = append(o.Fields, Field{Path: parts[0]}) case 2: // specific ordering order := parts[1] var desc bool switch order { case "asc": desc = false case "desc": desc = true default: // parse error return fmt.Errorf("unmarshal order by '%s': invalid format", s) } o.Fields = append(o.Fields, Field{Path: parts[0], Desc: desc}) case 0: fallthrough default: return fmt.Errorf("unmarshal order by '%s': invalid format", s) } } return nil } // ValidateForMessage validates that the ordering paths are syntactically valid and // refer to known fields in the specified message type. func (o OrderBy) ValidateForMessage(m proto.Message) error { fm := fieldmaskpb.FieldMask{ Paths: make([]string, 0, len(o.Fields)), } for _, field := range o.Fields { fm.Paths = append(fm.Paths, field.Path) } return fieldmask.Validate(&fm, m) } // ValidateForPaths validates that the ordering paths are syntactically valid and refer to one of the provided paths. func (o OrderBy) ValidateForPaths(paths ...string) error { FieldLoop: for _, field := range o.Fields { // Assumption that len(paths) is short enough that O(n^2) is not a problem. for _, path := range paths { if field.Path == path { continue FieldLoop } } return fmt.Errorf("invalid field path: %s", field.Path) } return nil }