mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-28 13:52:25 -05:00 
			
		
		
		
	* chore: update otel dependencies * refactor: combine tracing & metrics in observability package * chore: update example tracing compose file
		
			
				
	
	
		
			168 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
	
		
			4.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package runtime
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"sort"
 | |
| 
 | |
| 	"google.golang.org/protobuf/proto"
 | |
| 	"google.golang.org/protobuf/reflect/protoreflect"
 | |
| 	field_mask "google.golang.org/protobuf/types/known/fieldmaskpb"
 | |
| )
 | |
| 
 | |
| func getFieldByName(fields protoreflect.FieldDescriptors, name string) protoreflect.FieldDescriptor {
 | |
| 	fd := fields.ByName(protoreflect.Name(name))
 | |
| 	if fd != nil {
 | |
| 		return fd
 | |
| 	}
 | |
| 
 | |
| 	return fields.ByJSONName(name)
 | |
| }
 | |
| 
 | |
| // FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body.
 | |
| func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*field_mask.FieldMask, error) {
 | |
| 	fm := &field_mask.FieldMask{}
 | |
| 	var root interface{}
 | |
| 
 | |
| 	if err := json.NewDecoder(r).Decode(&root); err != nil {
 | |
| 		if errors.Is(err, io.EOF) {
 | |
| 			return fm, nil
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	queue := []fieldMaskPathItem{{node: root, msg: msg.ProtoReflect()}}
 | |
| 	for len(queue) > 0 {
 | |
| 		// dequeue an item
 | |
| 		item := queue[0]
 | |
| 		queue = queue[1:]
 | |
| 
 | |
| 		m, ok := item.node.(map[string]interface{})
 | |
| 		switch {
 | |
| 		case ok && len(m) > 0:
 | |
| 			// if the item is an object, then enqueue all of its children
 | |
| 			for k, v := range m {
 | |
| 				if item.msg == nil {
 | |
| 					return nil, errors.New("JSON structure did not match request type")
 | |
| 				}
 | |
| 
 | |
| 				fd := getFieldByName(item.msg.Descriptor().Fields(), k)
 | |
| 				if fd == nil {
 | |
| 					return nil, fmt.Errorf("could not find field %q in %q", k, item.msg.Descriptor().FullName())
 | |
| 				}
 | |
| 
 | |
| 				if isDynamicProtoMessage(fd.Message()) {
 | |
| 					for _, p := range buildPathsBlindly(string(fd.FullName().Name()), v) {
 | |
| 						newPath := p
 | |
| 						if item.path != "" {
 | |
| 							newPath = item.path + "." + newPath
 | |
| 						}
 | |
| 						queue = append(queue, fieldMaskPathItem{path: newPath})
 | |
| 					}
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				if isProtobufAnyMessage(fd.Message()) && !fd.IsList() {
 | |
| 					_, hasTypeField := v.(map[string]interface{})["@type"]
 | |
| 					if hasTypeField {
 | |
| 						queue = append(queue, fieldMaskPathItem{path: k})
 | |
| 						continue
 | |
| 					} else {
 | |
| 						return nil, fmt.Errorf("could not find field @type in %q in message %q", k, item.msg.Descriptor().FullName())
 | |
| 					}
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				child := fieldMaskPathItem{
 | |
| 					node: v,
 | |
| 				}
 | |
| 				if item.path == "" {
 | |
| 					child.path = string(fd.FullName().Name())
 | |
| 				} else {
 | |
| 					child.path = item.path + "." + string(fd.FullName().Name())
 | |
| 				}
 | |
| 
 | |
| 				switch {
 | |
| 				case fd.IsList(), fd.IsMap():
 | |
| 					// As per: https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/field_mask.proto#L85-L86
 | |
| 					// Do not recurse into repeated fields. The repeated field goes on the end of the path and we stop.
 | |
| 					fm.Paths = append(fm.Paths, child.path)
 | |
| 				case fd.Message() != nil:
 | |
| 					child.msg = item.msg.Get(fd).Message()
 | |
| 					fallthrough
 | |
| 				default:
 | |
| 					queue = append(queue, child)
 | |
| 				}
 | |
| 			}
 | |
| 		case ok && len(m) == 0:
 | |
| 			fallthrough
 | |
| 		case len(item.path) > 0:
 | |
| 			// otherwise, it's a leaf node so print its path
 | |
| 			fm.Paths = append(fm.Paths, item.path)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Sort for deterministic output in the presence
 | |
| 	// of repeated fields.
 | |
| 	sort.Strings(fm.Paths)
 | |
| 
 | |
| 	return fm, nil
 | |
| }
 | |
| 
 | |
| func isProtobufAnyMessage(md protoreflect.MessageDescriptor) bool {
 | |
| 	return md != nil && (md.FullName() == "google.protobuf.Any")
 | |
| }
 | |
| 
 | |
| func isDynamicProtoMessage(md protoreflect.MessageDescriptor) bool {
 | |
| 	return md != nil && (md.FullName() == "google.protobuf.Struct" || md.FullName() == "google.protobuf.Value")
 | |
| }
 | |
| 
 | |
| // buildPathsBlindly does not attempt to match proto field names to the
 | |
| // json value keys.  Instead it relies completely on the structure of
 | |
| // the unmarshalled json contained within in.
 | |
| // Returns a slice containing all subpaths with the root at the
 | |
| // passed in name and json value.
 | |
| func buildPathsBlindly(name string, in interface{}) []string {
 | |
| 	m, ok := in.(map[string]interface{})
 | |
| 	if !ok {
 | |
| 		return []string{name}
 | |
| 	}
 | |
| 
 | |
| 	var paths []string
 | |
| 	queue := []fieldMaskPathItem{{path: name, node: m}}
 | |
| 	for len(queue) > 0 {
 | |
| 		cur := queue[0]
 | |
| 		queue = queue[1:]
 | |
| 
 | |
| 		m, ok := cur.node.(map[string]interface{})
 | |
| 		if !ok {
 | |
| 			// This should never happen since we should always check that we only add
 | |
| 			// nodes of type map[string]interface{} to the queue.
 | |
| 			continue
 | |
| 		}
 | |
| 		for k, v := range m {
 | |
| 			if mi, ok := v.(map[string]interface{}); ok {
 | |
| 				queue = append(queue, fieldMaskPathItem{path: cur.path + "." + k, node: mi})
 | |
| 			} else {
 | |
| 				// This is not a struct, so there are no more levels to descend.
 | |
| 				curPath := cur.path + "." + k
 | |
| 				paths = append(paths, curPath)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	return paths
 | |
| }
 | |
| 
 | |
| // fieldMaskPathItem stores an in-progress deconstruction of a path for a fieldmask
 | |
| type fieldMaskPathItem struct {
 | |
| 	// the list of prior fields leading up to node connected by dots
 | |
| 	path string
 | |
| 
 | |
| 	// a generic decoded json object the current item to inspect for further path extraction
 | |
| 	node interface{}
 | |
| 
 | |
| 	// parent message
 | |
| 	msg protoreflect.Message
 | |
| }
 |