mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 22:32:25 -05:00 
			
		
		
		
	
		
			
	
	
		
			205 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			205 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package structr | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"reflect" | ||
|  | 	"strings" | ||
|  | 
 | ||
|  | 	"codeberg.org/gruf/go-byteutil" | ||
|  | 	"codeberg.org/gruf/go-mangler" | ||
|  | ) | ||
|  | 
 | ||
|  | // KeyGen is the underlying index key generator | ||
|  | // used within Index, and therefore Cache itself. | ||
|  | type KeyGen[StructType any] struct { | ||
|  | 
 | ||
|  | 	// fields contains our representation of | ||
|  | 	// the struct fields contained in the | ||
|  | 	// creation of keys by this generator. | ||
|  | 	fields []structfield | ||
|  | 
 | ||
|  | 	// zero specifies whether zero | ||
|  | 	// value fields are permitted. | ||
|  | 	zero bool | ||
|  | } | ||
|  | 
 | ||
|  | // NewKeyGen returns a new initialized KeyGen for the receiving generic | ||
|  | // parameter type, comprising of the given field strings, and whether to | ||
|  | // allow zero values to be included within generated output strings. | ||
|  | func NewKeyGen[T any](fields []string, allowZero bool) KeyGen[T] { | ||
|  | 	var kgen KeyGen[T] | ||
|  | 
 | ||
|  | 	// Preallocate expected struct field slice. | ||
|  | 	kgen.fields = make([]structfield, len(fields)) | ||
|  | 
 | ||
|  | 	// Get the reflected struct ptr type. | ||
|  | 	t := reflect.TypeOf((*T)(nil)).Elem() | ||
|  | 
 | ||
|  | 	for i, fieldName := range fields { | ||
|  | 		// Split name to account for nesting. | ||
|  | 		names := strings.Split(fieldName, ".") | ||
|  | 
 | ||
|  | 		// Look for a usable struct field from type. | ||
|  | 		sfield, ok := findField(t, names, allowZero) | ||
|  | 		if !ok { | ||
|  | 			panicf("failed finding field: %s", fieldName) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Set parsed struct field. | ||
|  | 		kgen.fields[i] = sfield | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Set config flags. | ||
|  | 	kgen.zero = allowZero | ||
|  | 
 | ||
|  | 	return kgen | ||
|  | } | ||
|  | 
 | ||
|  | // FromParts generates key string from individual key parts. | ||
|  | func (kgen *KeyGen[T]) FromParts(parts ...any) (key string, ok bool) { | ||
|  | 	buf := getBuf() | ||
|  | 	if ok = kgen.AppendFromParts(buf, parts...); ok { | ||
|  | 		key = string(buf.B) | ||
|  | 	} | ||
|  | 	putBuf(buf) | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | // FromValue generates key string from a value, via reflection. | ||
|  | func (kgen *KeyGen[T]) FromValue(value T) (key string, ok bool) { | ||
|  | 	buf := getBuf() | ||
|  | 	rvalue := reflect.ValueOf(value) | ||
|  | 	if ok = kgen.appendFromRValue(buf, rvalue); ok { | ||
|  | 		key = string(buf.B) | ||
|  | 	} | ||
|  | 	putBuf(buf) | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | // AppendFromParts generates key string into provided buffer, from individual key parts. | ||
|  | func (kgen *KeyGen[T]) AppendFromParts(buf *byteutil.Buffer, parts ...any) bool { | ||
|  | 	if len(parts) != len(kgen.fields) { | ||
|  | 		// User must provide correct number of parts for key. | ||
|  | 		panicf("incorrect number key parts: want=%d received=%d", | ||
|  | 			len(parts), | ||
|  | 			len(kgen.fields), | ||
|  | 		) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if kgen.zero { | ||
|  | 		// Zero values are permitted, | ||
|  | 		// mangle all values and ignore | ||
|  | 		// zero value return booleans. | ||
|  | 		for i, part := range parts { | ||
|  | 
 | ||
|  | 			// Mangle this value into buffer. | ||
|  | 			_ = kgen.fields[i].Mangle(buf, part) | ||
|  | 
 | ||
|  | 			// Append part separator. | ||
|  | 			buf.B = append(buf.B, '.') | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		// Zero values are NOT permitted. | ||
|  | 		for i, part := range parts { | ||
|  | 
 | ||
|  | 			// Mangle this value into buffer. | ||
|  | 			z := kgen.fields[i].Mangle(buf, part) | ||
|  | 
 | ||
|  | 			if z { | ||
|  | 				// The value was zero for | ||
|  | 				// this type, return early. | ||
|  | 				return false | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Append part separator. | ||
|  | 			buf.B = append(buf.B, '.') | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Drop the last separator. | ||
|  | 	buf.B = buf.B[:len(buf.B)-1] | ||
|  | 
 | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | // AppendFromValue generates key string into provided buffer, from a value via reflection. | ||
|  | func (kgen *KeyGen[T]) AppendFromValue(buf *byteutil.Buffer, value T) bool { | ||
|  | 	return kgen.appendFromRValue(buf, reflect.ValueOf(value)) | ||
|  | } | ||
|  | 
 | ||
|  | // appendFromRValue is the underlying generator function for the exported ___FromValue() functions, | ||
|  | // accepting a reflected input. We do not expose this as the reflected value is EXPECTED to be right. | ||
|  | func (kgen *KeyGen[T]) appendFromRValue(buf *byteutil.Buffer, rvalue reflect.Value) bool { | ||
|  | 	// Follow any ptrs leading to value. | ||
|  | 	for rvalue.Kind() == reflect.Pointer { | ||
|  | 		rvalue = rvalue.Elem() | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if kgen.zero { | ||
|  | 		// Zero values are permitted, | ||
|  | 		// mangle all values and ignore | ||
|  | 		// zero value return booleans. | ||
|  | 		for i := range kgen.fields { | ||
|  | 
 | ||
|  | 			// Get the reflect value's field at idx. | ||
|  | 			fv := rvalue.FieldByIndex(kgen.fields[i].index) | ||
|  | 			fi := fv.Interface() | ||
|  | 
 | ||
|  | 			// Mangle this value into buffer. | ||
|  | 			_ = kgen.fields[i].Mangle(buf, fi) | ||
|  | 
 | ||
|  | 			// Append part separator. | ||
|  | 			buf.B = append(buf.B, '.') | ||
|  | 		} | ||
|  | 	} else { | ||
|  | 		// Zero values are NOT permitted. | ||
|  | 		for i := range kgen.fields { | ||
|  | 
 | ||
|  | 			// Get the reflect value's field at idx. | ||
|  | 			fv := rvalue.FieldByIndex(kgen.fields[i].index) | ||
|  | 			fi := fv.Interface() | ||
|  | 
 | ||
|  | 			// Mangle this value into buffer. | ||
|  | 			z := kgen.fields[i].Mangle(buf, fi) | ||
|  | 
 | ||
|  | 			if z { | ||
|  | 				// The value was zero for | ||
|  | 				// this type, return early. | ||
|  | 				return false | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Append part separator. | ||
|  | 			buf.B = append(buf.B, '.') | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Drop the last separator. | ||
|  | 	buf.B = buf.B[:len(buf.B)-1] | ||
|  | 
 | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | type structfield struct { | ||
|  | 	// index is the reflected index | ||
|  | 	// of this field (this takes into | ||
|  | 	// account struct nesting). | ||
|  | 	index []int | ||
|  | 
 | ||
|  | 	// zero is the possible mangled | ||
|  | 	// zero value for this field. | ||
|  | 	zero string | ||
|  | 
 | ||
|  | 	// mangler is the mangler function for | ||
|  | 	// serializing values of this field. | ||
|  | 	mangler mangler.Mangler | ||
|  | } | ||
|  | 
 | ||
|  | // Mangle mangles the given value, using the determined type-appropriate | ||
|  | // field's type. The returned boolean indicates whether this is a zero value. | ||
|  | func (f *structfield) Mangle(buf *byteutil.Buffer, value any) (isZero bool) { | ||
|  | 	s := len(buf.B) // start pos. | ||
|  | 	buf.B = f.mangler(buf.B, value) | ||
|  | 	e := len(buf.B) // end pos. | ||
|  | 	isZero = (f.zero == string(buf.B[s:e])) | ||
|  | 	return | ||
|  | } |