...

Source file src/go.einride.tech/aip/ordering/orderby.go

Documentation: go.einride.tech/aip/ordering

     1  package ordering
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"unicode"
     8  
     9  	"go.einride.tech/aip/fieldmask"
    10  	"google.golang.org/protobuf/proto"
    11  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    12  )
    13  
    14  // OrderBy represents an ordering directive.
    15  type OrderBy struct {
    16  	// Fields are the fields to order by.
    17  	Fields []Field
    18  }
    19  
    20  // Field represents a single ordering field.
    21  type Field struct {
    22  	// Path is the path of the field, including subfields.
    23  	Path string
    24  	// Desc indicates if the ordering of the field is descending.
    25  	Desc bool
    26  }
    27  
    28  // SubFields returns the individual subfields of the field path, including the top-level subfield.
    29  //
    30  // Subfields are specified with a . character, such as foo.bar or address.street.
    31  func (f Field) SubFields() []string {
    32  	if f.Path == "" {
    33  		return nil
    34  	}
    35  	return strings.Split(f.Path, ".")
    36  }
    37  
    38  // UnmarshalString sets o from the provided ordering string. .
    39  func (o *OrderBy) UnmarshalString(s string) error {
    40  	o.Fields = o.Fields[:0]
    41  	if s == "" { // fast path for no ordering
    42  		return nil
    43  	}
    44  	for _, r := range s {
    45  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) && r != '_' && r != ' ' && r != ',' && r != '.' {
    46  			return fmt.Errorf("unmarshal order by '%s': invalid character %s", s, strconv.QuoteRune(r))
    47  		}
    48  	}
    49  	fields := strings.Split(s, ",")
    50  	o.Fields = make([]Field, 0, len(fields))
    51  	for _, field := range fields {
    52  		parts := strings.Fields(field)
    53  		switch len(parts) {
    54  		case 1: // default ordering (ascending)
    55  			o.Fields = append(o.Fields, Field{Path: parts[0]})
    56  		case 2: // specific ordering
    57  			order := parts[1]
    58  			var desc bool
    59  			switch order {
    60  			case "asc":
    61  				desc = false
    62  			case "desc":
    63  				desc = true
    64  			default: // parse error
    65  				return fmt.Errorf("unmarshal order by '%s': invalid format", s)
    66  			}
    67  			o.Fields = append(o.Fields, Field{Path: parts[0], Desc: desc})
    68  		case 0:
    69  			fallthrough
    70  		default:
    71  			return fmt.Errorf("unmarshal order by '%s': invalid format", s)
    72  		}
    73  	}
    74  	return nil
    75  }
    76  
    77  // ValidateForMessage validates that the ordering paths are syntactically valid and
    78  // refer to known fields in the specified message type.
    79  func (o OrderBy) ValidateForMessage(m proto.Message) error {
    80  	fm := fieldmaskpb.FieldMask{
    81  		Paths: make([]string, 0, len(o.Fields)),
    82  	}
    83  	for _, field := range o.Fields {
    84  		fm.Paths = append(fm.Paths, field.Path)
    85  	}
    86  	return fieldmask.Validate(&fm, m)
    87  }
    88  
    89  // ValidateForPaths validates that the ordering paths are syntactically valid and refer to one of the provided paths.
    90  func (o OrderBy) ValidateForPaths(paths ...string) error {
    91  FieldLoop:
    92  	for _, field := range o.Fields {
    93  		// Assumption that len(paths) is short enough that O(n^2) is not a problem.
    94  		for _, path := range paths {
    95  			if field.Path == path {
    96  				continue FieldLoop
    97  			}
    98  		}
    99  		return fmt.Errorf("invalid field path: %s", field.Path)
   100  	}
   101  	return nil
   102  }
   103  

View as plain text