Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 717065e62d | |||
| e7b2b040b2 | |||
| 05d0f9e469 | 
							
								
								
									
										111
									
								
								langext/reflection.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								langext/reflection.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | package langext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"reflect" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var reflectBasicTypes = []reflect.Type{ | ||||||
|  | 	reflect.Bool:       reflect.TypeOf(false), | ||||||
|  | 	reflect.Int:        reflect.TypeOf(int(0)), | ||||||
|  | 	reflect.Int8:       reflect.TypeOf(int8(0)), | ||||||
|  | 	reflect.Int16:      reflect.TypeOf(int16(0)), | ||||||
|  | 	reflect.Int32:      reflect.TypeOf(int32(0)), | ||||||
|  | 	reflect.Int64:      reflect.TypeOf(int64(0)), | ||||||
|  | 	reflect.Uint:       reflect.TypeOf(uint(0)), | ||||||
|  | 	reflect.Uint8:      reflect.TypeOf(uint8(0)), | ||||||
|  | 	reflect.Uint16:     reflect.TypeOf(uint16(0)), | ||||||
|  | 	reflect.Uint32:     reflect.TypeOf(uint32(0)), | ||||||
|  | 	reflect.Uint64:     reflect.TypeOf(uint64(0)), | ||||||
|  | 	reflect.Uintptr:    reflect.TypeOf(uintptr(0)), | ||||||
|  | 	reflect.Float32:    reflect.TypeOf(float32(0)), | ||||||
|  | 	reflect.Float64:    reflect.TypeOf(float64(0)), | ||||||
|  | 	reflect.Complex64:  reflect.TypeOf(complex64(0)), | ||||||
|  | 	reflect.Complex128: reflect.TypeOf(complex128(0)), | ||||||
|  | 	reflect.String:     reflect.TypeOf(""), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Underlying returns the underlying type of t (without type alias) | ||||||
|  | // | ||||||
|  | // https://github.com/golang/go/issues/39574#issuecomment-655664772 | ||||||
|  | func Underlying(t reflect.Type) (ret reflect.Type) { | ||||||
|  | 	if t.Name() == "" { | ||||||
|  | 		// t is an unnamed type. the underlying type is t itself | ||||||
|  | 		return t | ||||||
|  | 	} | ||||||
|  | 	kind := t.Kind() | ||||||
|  | 	if ret = reflectBasicTypes[kind]; ret != nil { | ||||||
|  | 		return ret | ||||||
|  | 	} | ||||||
|  | 	switch kind { | ||||||
|  | 	case reflect.Array: | ||||||
|  | 		ret = reflect.ArrayOf(t.Len(), t.Elem()) | ||||||
|  | 	case reflect.Chan: | ||||||
|  | 		ret = reflect.ChanOf(t.ChanDir(), t.Elem()) | ||||||
|  | 	case reflect.Map: | ||||||
|  | 		ret = reflect.MapOf(t.Key(), t.Elem()) | ||||||
|  | 	case reflect.Func: | ||||||
|  | 		nIn := t.NumIn() | ||||||
|  | 		nOut := t.NumOut() | ||||||
|  | 		in := make([]reflect.Type, nIn) | ||||||
|  | 		out := make([]reflect.Type, nOut) | ||||||
|  | 		for i := 0; i < nIn; i++ { | ||||||
|  | 			in[i] = t.In(i) | ||||||
|  | 		} | ||||||
|  | 		for i := 0; i < nOut; i++ { | ||||||
|  | 			out[i] = t.Out(i) | ||||||
|  | 		} | ||||||
|  | 		ret = reflect.FuncOf(in, out, t.IsVariadic()) | ||||||
|  | 	case reflect.Interface: | ||||||
|  | 		// not supported | ||||||
|  | 	case reflect.Ptr: | ||||||
|  | 		ret = reflect.PtrTo(t.Elem()) | ||||||
|  | 	case reflect.Slice: | ||||||
|  | 		ret = reflect.SliceOf(t.Elem()) | ||||||
|  | 	case reflect.Struct: | ||||||
|  | 		// only partially supported: embedded fields | ||||||
|  | 		// and unexported fields may cause panic in reflect.StructOf() | ||||||
|  | 		defer func() { | ||||||
|  | 			// if a panic happens, return t unmodified | ||||||
|  | 			if recover() != nil && ret == nil { | ||||||
|  | 				ret = t | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 		n := t.NumField() | ||||||
|  | 		fields := make([]reflect.StructField, n) | ||||||
|  | 		for i := 0; i < n; i++ { | ||||||
|  | 			fields[i] = t.Field(i) | ||||||
|  | 		} | ||||||
|  | 		ret = reflect.StructOf(fields) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TryCast works similar to `v2, ok := v.(T)` | ||||||
|  | // Except it works through type alias' | ||||||
|  | func TryCast[T any](v any) (T, bool) { | ||||||
|  |  | ||||||
|  | 	underlying := Underlying(reflect.TypeOf(v)) | ||||||
|  |  | ||||||
|  | 	def := *new(T) | ||||||
|  |  | ||||||
|  | 	if underlying != Underlying(reflect.TypeOf(def)) { | ||||||
|  | 		return def, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r1 := reflect.ValueOf(v) | ||||||
|  |  | ||||||
|  | 	if !r1.CanConvert(underlying) { | ||||||
|  | 		return def, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r2 := r1.Convert(underlying) | ||||||
|  |  | ||||||
|  | 	r3 := r2.Interface() | ||||||
|  |  | ||||||
|  | 	r4, ok := r3.(T) | ||||||
|  | 	if !ok { | ||||||
|  | 		return def, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r4, true | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								mongoext/pipeline.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								mongoext/pipeline.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | package mongoext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson" | ||||||
|  | 	"go.mongodb.org/mongo-driver/mongo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FixTextSearchPipeline moves {$match:{$text:{$search}}} entries to the front of the pipeline (otherwise its an mongo error) | ||||||
|  | func FixTextSearchPipeline(pipeline mongo.Pipeline) mongo.Pipeline { | ||||||
|  |  | ||||||
|  | 	dget := func(v bson.D, k string) (bson.M, bool) { | ||||||
|  | 		for _, e := range v { | ||||||
|  | 			if e.Key == k { | ||||||
|  | 				if mv, ok := e.Value.(bson.M); ok { | ||||||
|  | 					return mv, true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  | 	mget := func(v bson.M, k string) (bson.M, bool) { | ||||||
|  | 		for ekey, eval := range v { | ||||||
|  | 			if ekey == k { | ||||||
|  | 				if mv, ok := eval.(bson.M); ok { | ||||||
|  | 					return mv, true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result := make([]bson.D, 0, len(pipeline)) | ||||||
|  |  | ||||||
|  | 	for _, entry := range pipeline { | ||||||
|  |  | ||||||
|  | 		if v0, ok := dget(entry, "$match"); ok { | ||||||
|  | 			if v1, ok := mget(v0, "$text"); ok { | ||||||
|  | 				if _, ok := v1["$search"]; ok { | ||||||
|  | 					result = append([]bson.D{entry}, result...) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		result = append(result, entry) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								mongoext/projections.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								mongoext/projections.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | package mongoext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ProjectionFromStruct automatically generated a mongodb projection for a struct | ||||||
|  | // This way you can pretty much always write | ||||||
|  | // `options.FindOne().SetProjection(mongoutils.ProjectionFromStruct(...your_model...))` | ||||||
|  | // to only get the data from mongodb that you will actually use in the later decode step | ||||||
|  | func ProjectionFromStruct(obj interface{}) bson.M { | ||||||
|  | 	v := reflect.ValueOf(obj) | ||||||
|  | 	t := v.Type() | ||||||
|  |  | ||||||
|  | 	result := bson.M{} | ||||||
|  |  | ||||||
|  | 	for i := 0; i < v.NumField(); i++ { | ||||||
|  | 		tag := t.Field(i).Tag.Get("bson") | ||||||
|  | 		if tag == "" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		tag = strings.Split(tag, ",")[0] | ||||||
|  |  | ||||||
|  | 		result[tag] = 1 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result | ||||||
|  | } | ||||||
| @@ -71,6 +71,7 @@ func (t *RFC3339Time) UnmarshalText(data []byte) error { | |||||||
| func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | func (t *RFC3339Time) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | ||||||
| 	if bt == bsontype.Null { | 	if bt == bsontype.Null { | ||||||
| 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | ||||||
|  | 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | ||||||
| 		// https://stackoverflow.com/questions/75167597 | 		// https://stackoverflow.com/questions/75167597 | ||||||
| 		// https://jira.mongodb.org/browse/GODRIVER-2252 | 		// https://jira.mongodb.org/browse/GODRIVER-2252 | ||||||
| 		*t = RFC3339Time{} | 		*t = RFC3339Time{} | ||||||
|   | |||||||
| @@ -71,6 +71,7 @@ func (t *RFC3339NanoTime) UnmarshalText(data []byte) error { | |||||||
| func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | func (t *RFC3339NanoTime) UnmarshalBSONValue(bt bsontype.Type, data []byte) error { | ||||||
| 	if bt == bsontype.Null { | 	if bt == bsontype.Null { | ||||||
| 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | 		// we can't set nil in UnmarshalBSONValue (so we use default(struct)) | ||||||
|  | 		// Use mongoext.CreateGoExtBsonRegistry if you need to unmarsh pointer values | ||||||
| 		// https://stackoverflow.com/questions/75167597 | 		// https://stackoverflow.com/questions/75167597 | ||||||
| 		// https://jira.mongodb.org/browse/GODRIVER-2252 | 		// https://jira.mongodb.org/browse/GODRIVER-2252 | ||||||
| 		*t = RFC3339NanoTime{} | 		*t = RFC3339NanoTime{} | ||||||
| @@ -115,7 +116,11 @@ func (t RFC3339NanoTime) DecodeValue(dc bsoncodec.DecodeContext, vr bsonrw.Value | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if val.Kind() == reflect.Ptr { | ||||||
|  | 		val.Set(reflect.ValueOf(&t)) | ||||||
|  | 	} else { | ||||||
| 		val.Set(reflect.ValueOf(t)) | 		val.Set(reflect.ValueOf(t)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user