Welcome to mirror list, hosted at ThFree Co, Russian Federation.

targetrepo.go « protoregistry « praefect « internal - gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 16afe395c880dc0c50786a0a2be430f30dac6084 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package protoregistry

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"

	"github.com/golang/protobuf/proto"
	"gitlab.com/gitlab-org/gitaly/proto/go/gitalypb"
)

const (
	protobufTag      = "protobuf"
	protobufOneOfTag = "protobuf_oneof"
)

// reflectFindRepoTarget finds the target repository by using the OID to
// navigate the struct tags
// Warning: this reflection filled function is full of forbidden dark elf magic
func reflectFindRepoTarget(pbMsg proto.Message, targetOID []int) (*gitalypb.Repository, error) {
	var targetRepo *gitalypb.Repository

	msgV := reflect.ValueOf(pbMsg)

	for _, fieldNo := range targetOID {
		var err error

		msgV, err = findProtoField(msgV, fieldNo)
		if err != nil {
			return nil, fmt.Errorf(
				"unable to descend OID %+v into message %s: %s",
				targetOID, proto.MessageName(pbMsg), err,
			)
		}
	}

	targetRepo, ok := msgV.Interface().(*gitalypb.Repository)
	if !ok {
		return nil, fmt.Errorf("repo target OID %v points to non-Repo type %+v", targetOID, msgV.Interface())
	}

	return targetRepo, nil
}

// matches a tag string like "bytes,1,opt,name=repository,proto3"
var protobufTagRegex = regexp.MustCompile(`^(.*?),(\d+),(.*?),name=(.*?),proto3(\,oneof)?$`)

const (
	protobufTagRegexGroups     = 6
	protobufTagRegexFieldGroup = 2
)

func findProtoField(msgV reflect.Value, protoField int) (reflect.Value, error) {
	msgV = reflect.Indirect(msgV)
	for i := 0; i < msgV.NumField(); i++ {
		field := msgV.Type().Field(i)

		ok, err := tryNumberedField(field, protoField)
		if err != nil {
			return reflect.Value{}, err
		}
		if ok {
			return msgV.FieldByName(field.Name), nil
		}

		oneofField, ok := tryOneOfField(msgV, field, protoField)
		if !ok {
			continue
		}
		return oneofField, nil
	}

	err := fmt.Errorf(
		"unable to find protobuf field %d in message %s",
		protoField, msgV.Type().Name(),
	)
	return reflect.Value{}, err
}

func tryNumberedField(field reflect.StructField, protoField int) (bool, error) {
	tag := field.Tag.Get(protobufTag)
	matches := protobufTagRegex.FindStringSubmatch(tag)
	if len(matches) == protobufTagRegexGroups {
		fieldStr := matches[protobufTagRegexFieldGroup]
		if fieldStr == strconv.Itoa(protoField) {
			return true, nil
		}
	}

	return false, nil
}

func tryOneOfField(msgV reflect.Value, field reflect.StructField, protoField int) (reflect.Value, bool) {
	oneOfTag := field.Tag.Get(protobufOneOfTag)
	if oneOfTag == "" {
		return reflect.Value{}, false // empty tag means this is not a oneOf field
	}

	// try all of the oneOf fields until a match is found
	msgV = msgV.FieldByName(field.Name).Elem().Elem()
	for i := 0; i < msgV.NumField(); i++ {
		field = msgV.Type().Field(i)

		ok, err := tryNumberedField(field, protoField)
		if err != nil {
			return reflect.Value{}, false
		}
		if ok {
			return msgV.FieldByName(field.Name), true
		}
	}

	return reflect.Value{}, false
}