github.com/diamondburned/arikawa/v2@v2.1.0/bot/extras/infer/infer.go (about)

     1  // Package infer implements reflect functions that package bot uses.
     2  //
     3  // Functions in this package may run recursively forever. This shouldn't happen
     4  // with Arikawa's structures, but use these functions with care.
     5  package infer
     6  
     7  import (
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/diamondburned/arikawa/v2/discord"
    12  )
    13  
    14  // ChannelID looks for fields with name ChannelID, Channel, or in some special
    15  // cases, ID.
    16  func ChannelID(event interface{}) discord.ChannelID {
    17  	return discord.ChannelID(reflectID(reflect.ValueOf(event), "Channel"))
    18  }
    19  
    20  // GuildID looks for fields with name GuildID, Guild, or in some special cases,
    21  // ID.
    22  func GuildID(event interface{}) discord.GuildID {
    23  	return discord.GuildID(reflectID(reflect.ValueOf(event), "Guild"))
    24  }
    25  
    26  // UserID looks for fields with name UserID, User, or in some special cases, ID.
    27  func UserID(event interface{}) discord.UserID {
    28  	// This may have a very fatal bug of accidentally mistaking another User's
    29  	// ID. It also probably wouldn't work with things like RecipientID.
    30  	return discord.UserID(reflectID(reflect.ValueOf(event), "User"))
    31  }
    32  
    33  func reflectID(v reflect.Value, thing string) discord.Snowflake {
    34  	if !v.IsValid() {
    35  		return 0
    36  	}
    37  
    38  	t := v.Type()
    39  
    40  	if t.Kind() == reflect.Ptr {
    41  		v = v.Elem()
    42  
    43  		// Recheck after dereferring
    44  		if !v.IsValid() {
    45  			return 0
    46  		}
    47  
    48  		t = v.Type()
    49  	}
    50  
    51  	if t.Kind() != reflect.Struct {
    52  		return 0
    53  	}
    54  
    55  	numFields := t.NumField()
    56  
    57  	for i := 0; i < numFields; i++ {
    58  		field := t.Field(i)
    59  		fType := field.Type
    60  
    61  		if fType.Kind() == reflect.Ptr {
    62  			fType = fType.Elem()
    63  		}
    64  
    65  		switch fType.Kind() {
    66  		case reflect.Struct:
    67  			if chID := reflectID(v.Field(i), thing); chID.IsValid() {
    68  				return chID
    69  			}
    70  		case reflect.Uint64:
    71  			switch {
    72  			case false,
    73  				// Contains works with "LastMessageID" and such.
    74  				strings.Contains(field.Name, thing+"ID"),
    75  				// Special case where the struct name has Channel in it.
    76  				field.Name == "ID" && strings.Contains(t.Name(), thing):
    77  
    78  				return discord.Snowflake(v.Field(i).Uint())
    79  			}
    80  		}
    81  	}
    82  
    83  	return 0
    84  }
    85  
    86  /*
    87  var reflectCache sync.Map
    88  
    89  type cacheKey struct {
    90  	t reflect.Type
    91  	f string
    92  }
    93  
    94  func getID(v reflect.Value, thing string) discord.Snowflake {
    95  	if !v.IsValid() {
    96  		return 0
    97  	}
    98  
    99  	t := v.Type()
   100  
   101  	if t.Kind() == reflect.Ptr {
   102  		v = v.Elem()
   103  
   104  		// Recheck after dereferring
   105  		if !v.IsValid() {
   106  			return 0
   107  		}
   108  
   109  		t = v.Type()
   110  	}
   111  
   112  	if t.Kind() != reflect.Struct {
   113  		return 0
   114  	}
   115  
   116  	return reflectID(thing, v, t)
   117  }
   118  
   119  type reflector struct {
   120  	steps   []step
   121  	thing   string
   122  	thingID string
   123  }
   124  
   125  type step struct {
   126  	field int
   127  	ptr   bool
   128  	rec   []step
   129  }
   130  
   131  func reflectID(thing string, v reflect.Value, t reflect.Type) discord.Snowflake {
   132  	r := &reflector{thing: thing}
   133  
   134  	// copy original type
   135  	key := r.thing + t.String()
   136  
   137  	// check the cache
   138  	if instructions, ok := reflectCache.Load(key); ok {
   139  		if instructions == nil {
   140  			return 0
   141  		}
   142  		return applyInstructions(v, instructions.([]step))
   143  	}
   144  
   145  	r.thingID = r.thing + "ID"
   146  	r.steps = make([]step, 0, 1)
   147  	id := r._id(v, t)
   148  
   149  	if r.steps != nil {
   150  		reflectCache.Store(key, r.instructions())
   151  	}
   152  
   153  	return id
   154  }
   155  
   156  func applyInstructions(v reflect.Value, instructions []step) discord.Snowflake {
   157  	// Use a type here to detect recursion:
   158  	// var originalT = v.Type()
   159  	var laststep reflect.Value
   160  
   161  	log.Println(v.Type(), instructions)
   162  
   163  	for i, step := range instructions {
   164  		if !v.IsValid() {
   165  			return 0
   166  		}
   167  		if i > 0 && step.ptr {
   168  			v = v.Elem()
   169  		}
   170  		if !v.IsValid() {
   171  			// is this the bottom of the instructions?
   172  			if i == len(instructions)-1 && step.rec != nil {
   173  				for _, ins := range step.rec {
   174  					var value = laststep.Field(ins.field)
   175  					if ins.ptr {
   176  						value = value.Elem()
   177  					}
   178  					if id := applyInstructions(value, instructions); id.IsValid() {
   179  						return id
   180  					}
   181  				}
   182  			}
   183  			return 0
   184  		}
   185  		laststep = v
   186  		v = laststep.Field(step.field)
   187  	}
   188  	return discord.Snowflake(v.Int())
   189  }
   190  
   191  func (r *reflector) instructions() []step {
   192  	if len(r.steps) == 0 {
   193  		return nil
   194  	}
   195  	var instructions = make([]step, len(r.steps))
   196  	for i := 0; i < len(instructions); i++ {
   197  		instructions[i] = r.steps[len(r.steps)-i-1]
   198  	}
   199  	// instructions := r.steps
   200  	return instructions
   201  }
   202  
   203  func (r *reflector) step(s step) {
   204  	r.steps = append(r.steps, s)
   205  }
   206  
   207  func (r *reflector) _id(v reflect.Value, t reflect.Type) (chID discord.Snowflake) {
   208  	numFields := t.NumField()
   209  
   210  	var ptr bool
   211  	var ins = step{field: -1}
   212  
   213  	for i := 0; i < numFields; i++ {
   214  		field := t.Field(i)
   215  		fType := field.Type
   216  		value := v.Field(i)
   217  		ptr = false
   218  
   219  		if fType.Kind() == reflect.Ptr {
   220  			fType = fType.Elem()
   221  			value = value.Elem()
   222  			ptr = true
   223  		}
   224  
   225  		// does laststep have the same field type?
   226  		if fType == t {
   227  			ins.rec = append(ins.rec, step{field: i, ptr: ptr})
   228  		}
   229  
   230  		if !value.IsValid() {
   231  			continue
   232  		}
   233  
   234  		// If we've already found the field:
   235  		if ins.field > 0 {
   236  			continue
   237  		}
   238  
   239  		switch fType.Kind() {
   240  		case reflect.Struct:
   241  			if chID = r._id(value, fType); chID.IsValid() {
   242  				ins.field = i
   243  				ins.ptr = ptr
   244  			}
   245  		case reflect.Int64:
   246  			switch {
   247  			case false,
   248  				// Contains works with "LastMessageID" and such.
   249  				strings.Contains(field.Name, r.thingID),
   250  				// Special case where the struct name has Channel in it.
   251  				field.Name == "ID" && strings.Contains(t.Name(), r.thing):
   252  
   253  				ins.field = i
   254  				ins.ptr = ptr
   255  
   256  				chID = discord.Snowflake(value.Int())
   257  			}
   258  		}
   259  	}
   260  
   261  	// If we've found the field:
   262  	r.step(ins)
   263  
   264  	return
   265  }
   266  */