// ------------------------------------------------------------------------ // 🚀 Project active3 // 🔗 Active Thing (activething.com) git.activething.com/go // // File name key_stringer.go // ✍️ Created by DEV // 📅 Modified 11/05/2024 // // 🔒 Copyright 2024 activething.com // // 📜 LICENSE -------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------ package pkjson import ( "apack" "cdcs/cjson" "encoding" "errs" "reflect" "sync" "time" ) type ( keyStringer struct { sync.RWMutex stringers map[reflect.Type]rMapEntryFnc ifaceCheckers []func(p reflect.Type) rMapEntryFnc } keyStringerFnc func (s *keyStringer) ) func newKeyStringer (options...keyStringerFnc) *keyStringer { s := &keyStringer{ stringers: make(map[reflect.Type]rMapEntryFnc), } for _,o := range options { o(s) } return s } func (s *keyStringer) packStringifyKey (b *Builder, key any) { switch v := key.(type) { case string : b.packString(v,apack.FlagScapeHTML) case bool : cjson.WriteBool (b.B,v,true) case uint : cjson.WriteUint (b.B,uint64(v),true) case uint8 : cjson.WriteUint (b.B,uint64(v),true) case uint16 : cjson.WriteUint (b.B,uint64(v),true) case uint32 : cjson.WriteUint (b.B,uint64(v),true) case uintptr : cjson.WriteUint (b.B,uint64(v),true) case uint64 : cjson.WriteUint (b.B,v,true) case int : cjson.WriteInt (b.B,int64(v),true) case int8 : cjson.WriteInt (b.B,int64(v),true) case int16 : cjson.WriteInt (b.B,int64(v),true) case int32 : cjson.WriteInt (b.B,int64(v),true) case int64 : cjson.WriteInt (b.B,v,true) case float64 : cjson.WriteFloat (b.B,v,64,true) case float32 : cjson.WriteFloat (b.B,float64(v),32,true) case time.Time : cjson.WriteTime (b.B,v,b.f.DateFormat(),true) case time.Duration : cjson.WriteInt (b.B,int64(v),true) case error : if ec,is := v.(errs.ErrCoder); is { b.packString(ec.Code(),apack.FlagScapeHTML) return } b.packString(v.Error(),apack.FlagScapeHTML) default: me := &rMapEntry{ v: reflect.ValueOf(key) } fn := g.keyStringer.getOrBuildStringer(me.v.Type()) if fn == nil { b.SetError(apack.G.ErrInvalidKey.WithMessage("cant convert Key to string")) return } if er := fn(me,me.v); er != nil { b.SetError(er) return } b.packString(me.k,me.f) } } func (s *keyStringer) getOrBuildStringer(rtype reflect.Type) rMapEntryFnc { s.RLock() if fn,hs := s.stringers[rtype]; hs { s.Unlock() return fn } fn := s.checkInterfaces(rtype) if fn == nil && rtype.Kind() == reflect.Interface { fn = s.interfaceStringer(rtype) } if fn != nil { s.Lock() s.stringers[rtype]=fn s.Unlock() } return fn } func (s *keyStringer) interfaceStringer(rtype reflect.Type) rMapEntryFnc { return func(e *rMapEntry, k reflect.Value) error { k = k.Elem() if k.Type().Kind() == reflect.String { e.k = k.String() e.f = apack.FlagScapeHTML return nil } if t, h := k.Interface().(stringify); h { if k.Type().Kind() == reflect.Pointer && k.IsNil() { return nil } e.k = t.String() e.f = apack.FlagScapeHTML return nil } if t, h := k.Interface().(encoding.TextMarshaler); h { if k.Type().Kind() == reflect.Pointer && k.IsNil() { return nil } bf, er := t.MarshalText() if er == nil { e.k = string(bf) e.f = apack.FlagScapeHTML } return er } s.RLock() fn := s.stringers[k.Type()] s.RUnlock() if fn != nil { return fn(e,k) } return apack.G.ErrInvalidKey } } func (s *keyStringer) RegKeyStringer (rtype reflect.Type, stringerFn rMapEntryFnc) { if stringerFn == nil { return } s.Lock() s.stringers[rtype]=stringerFn s.Unlock() } func (s *keyStringer) RegInterfaceChecker (checkfn ... func(p reflect.Type) rMapEntryFnc) { s.ifaceCheckers = append(s.ifaceCheckers,checkfn ...) } func (s *keyStringer) checkInterfaces (rtype reflect.Type) rMapEntryFnc { for _,cf := range s.ifaceCheckers { if fn := cf(rtype); fn != nil { return fn } } return nil }