package neffos import ( "reflect" "strings" ) func indirectType(typ reflect.Type) reflect.Type { if typ.Kind() == reflect.Ptr { typ = typ.Elem() } return typ } func isZero(v reflect.Value) bool { switch v.Kind() { case reflect.Struct: zero := true for i := 0; i < v.NumField(); i++ { zero = zero && isZero(v.Field(i)) } if typ := v.Type(); typ != nil && v.IsValid() { f, ok := typ.MethodByName("IsZero") // if not found // if has input arguments (1 is for the value receiver, so > 1 for the actual input args) // if output argument is not boolean // then skip this IsZero user-defined function. if !ok || f.Type.NumIn() > 1 || f.Type.NumOut() != 1 && f.Type.Out(0).Kind() != reflect.Bool { return zero } method := v.Method(f.Index) // no needed check but: if method.IsValid() && !method.IsNil() { // it shouldn't panic here. zero = method.Call(nil)[0].Interface().(bool) } } return zero case reflect.Func, reflect.Map, reflect.Slice: return v.IsNil() case reflect.Array: zero := true for i := 0; i < v.Len(); i++ { zero = zero && isZero(v.Index(i)) } return zero } // if not any special type then use the reflect's .Zero // usually for fields, but remember if it's boolean and it's false // then it's zero, even if set-ed. if !v.CanInterface() { // if can't interface, i.e return value from unexported field or method then return false return false } zero := reflect.Zero(v.Type()) return v.Interface() == zero.Interface() } // does not support child elements on purpose. func visitFields(typ reflect.Type, visitor func(f reflect.StructField) bool) int { typ = indirectType(typ) for n, i := typ.NumField(), 0; i < n; i++ { f := typ.Field(i) found := visitor(f) if found { return i } } return -1 } func getNonZeroFields(v reflect.Value) (fields map[int]reflect.Value) { v = reflect.Indirect(v) visitFields(v.Type(), func(f reflect.StructField) bool { fieldIndex := f.Index[0] fieldValue := v.Field(fieldIndex) if !isZero(fieldValue) { if fields == nil { fields = make(map[int]reflect.Value) } fields[fieldIndex] = fieldValue } return false }) return } func getFieldIndex(forType reflect.Type, fieldType reflect.Type) int { return visitFields(forType, func(f reflect.StructField) bool { return f.Type == fieldType }) } func resolveStructNamespace(v reflect.Value) (string, bool) { // By Namespace() string method. typ := v.Type() method, ok := typ.MethodByName("Namespace") if ok { if getNamespace, ok := v.Method(method.Index).Interface().(func() string); ok { namespace := getNamespace() Debugf("Set namespace [\"%s\"] from method [%s.%s]", func() dargs { return dargs{namespace, nameOf(typ), method.Name} }) return namespace, true } } // By field Namespace string with filled value. typ = indirectType(typ) v = reflect.Indirect(v) if f, ok := typ.FieldByNameFunc(func(s string) bool { return s == "Namespace" }); ok { if f.Type.Kind() == reflect.String { namespace := v.Field(f.Index[0]).String() Debugf("Set namespace [\"%s\"] from field [%s.%s]", func() dargs { return dargs{namespace, nameOf(typ), f.Name} }) return namespace, true } } return "", false } var ( nsConnType = reflect.TypeOf((*NSConn)(nil)) msgType = reflect.TypeOf(Message{}) errType = reflect.TypeOf((*error)(nil)).Elem() ) func makeMessageHandlerFuncType(forType reflect.Type, nsConnFieldIndex int) reflect.Type { // Create the dynamic type which methods will be compared to. // remember, the receiver Ptr is also part of the input arguments, // that's why we don't use a static type assertion. expectedIn := []reflect.Type{ forType, nsConnType, msgType, } if nsConnFieldIndex >= 0 { // Except when the Ptr is a dynamic one (has a field of NSConn) then the event callback does not require // that on its input arguments. expectedIn = append(expectedIn[0:1], expectedIn[2:]...) } return reflect.FuncOf(expectedIn, []reflect.Type{errType}, false) } func isArgOf(fnType reflect.Type, argType reflect.Type) bool { if fnType.Kind() != reflect.Func { panic("isArgOf used on a non-method type") } for i, n := 0, fnType.NumIn(); i < n; i++ { if fnType.In(i) == argType { return true } } return false } func makeEventFromMethod(v reflect.Value, method reflect.Method, eventMatcher EventMatcherFunc) (eventName string, cb MessageHandlerFunc) { eventName = method.Name // if method looks like a system event, i.e // OnNamespaceConnected, then convert its registered event name // _OnNamespaceConnected which is the correct. // We could accept a func like: // func(s *myConn) _OnNamespaceConnected(msg neffos.Message) error // but Go linting does not allow this and // we don't want our users to have yellow boxes everywhere in their editors. if IsSystemEvent("_" + eventName) { eventName = "_" + eventName } if !IsSystemEvent(eventName) { if eventMatcher != nil { newName, ok := eventMatcher(method.Name) if !ok { return "", nil } eventName = newName } } if isArgOf(method.Type, nsConnType) { // it should accept NSConn - static "controller". cb = v.Method(method.Index).Interface().(func(*NSConn, Message) error) } else { // the NSConn exists on the "controller" itself which is set dynamically. cb = func(c *NSConn, msg Message) error { // load an existing instance which contains the same "c". return c.value.Method(method.Index).Interface().(func(Message) error)(msg) } } return } // StructInjector is a type which injects a dynamic struct value. // See `Struct.SetInjector` for more. type StructInjector func(structType reflect.Type, nsConn *NSConn) (structValue reflect.Value) func nameOf(structType reflect.Type) string { structType = indirectType(structType) typName := structType.Name() pkgPath := structType.PkgPath() fullname := pkgPath[strings.LastIndexByte(pkgPath, '/')+1:] + "." + typName return fullname } func makeEventsFromStruct(v reflect.Value, eventMatcher EventMatcherFunc, injector StructInjector) Events { events := make(Events) typ := v.Type() // get the index of field of a "NSConn" type. nsConnFieldIndex := getFieldIndex(typ, nsConnType) msgHandlerType := makeMessageHandlerFuncType(typ, nsConnFieldIndex) for i, n := 0, typ.NumMethod(); i < n; i++ { method := typ.Method(i) if method.Type != msgHandlerType { continue } eventName, cb := makeEventFromMethod(v, method, eventMatcher) if cb == nil { continue } Debugf("Event [\"%s\"] is handled by [%s.%s] method", func() dargs { return dargs{eventName, nameOf(typ), method.Name} }) events[eventName] = cb } if nsConnFieldIndex != -1 { typ = indirectType(typ) var staticFields map[int]reflect.Value if injector == nil { // maybe this should be added no matter what, I have to check // some things in our company's production server first. staticFields = getNonZeroFields(v) DebugEach(staticFields, func(idx int, f reflect.Value) { fval := f.Interface() fname := typ.Field(idx).Name if fname == "Namespace" { // let's no log this as user field because // it's optionally used to provide a namespace on NewStruct.GetNamespaces(). return } Debugf("Field [%s.%s] marked as static on value [%v]", nameOf(typ), fname, fval) }) injector = func(typ reflect.Type, nsConn *NSConn) reflect.Value { return reflect.New(typ) } } cb, hasNamespaceConnect := events[OnNamespaceConnect] events[OnNamespaceConnect] = func(c *NSConn, msg Message) error { cachePtr := injector(typ, c) cacheElem := cachePtr.Elem() // set the NSConn dynamic field. cacheElem.Field(nsConnFieldIndex).Set(reflect.ValueOf(c)) // set any static fields if default injector (see above). for findex, fvalue := range staticFields { cacheElem.Field(findex).Set(fvalue) } // Store it for the rest of the events inside // this namespace of that specific connection. c.value = cachePtr if hasNamespaceConnect { return cb(c, msg) } return nil } } return events }