// ------------------------------------------------------------------------ // Project active2 // Active Thing (activething.com) git.activething.com/go // // File name text_builder.go // Created by DEV // Modified 20/04/2024 // // Copyright 2024 activething.com // ------------------------------------------------------------------------ // 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 efjson import ( "bfrs" "cdcs/cjson" "encoding/json" "errs/erdata" "flds" "fmt" "strings" ) type ( jsonBuilder struct { f *JSONFormatter b *bfrs.ABuffer encoder *json.Encoder sep string pretty bool invalid error } ) func newJsonBuilder (f *JSONFormatter) *jsonBuilder { sep := ":" if f.HasUseSpace() && f.HasShowPretty() { sep = ": " } return &jsonBuilder{ f : f, b : bfrs.NewABuffer(), pretty : f.HasShowPretty(), sep : sep, } } func (b *jsonBuilder) format (err error) { cl := erdata.NewErrorData(err) cl.Collect(b.f.HasAddFields(),b.f.HasAddCauses(),b.f.HasAddTrace()) if cl.Invalid != nil { b.writeFormatError(cl.Invalid) return } b.write(cl,false, 0) } func (b *jsonBuilder) write (coll *erdata.ErrorData, cont bool, level int) { if b.invalid != nil { return } d := false defer func(dn *bool, bl *jsonBuilder){ if !(*dn) { if rc := recover(); rc != nil { if er,is := rc.(error); is { bl.invalid = er } else { bl.invalid = fmt.Errorf("%#v",rc) } bl.writeFormatError(bl.invalid) } } }(&d,b) var ( indLvl, indObj, ind2Obj string ) if b.pretty { if level > 0 { indLvl = strings.Repeat(b.f.Indent, 1+level) indObj = strings.Repeat(b.f.Indent, 2+level) ind2Obj = strings.Repeat(b.f.Indent, 3+level) } else { indObj = b.f.Indent ind2Obj = strings.Repeat(b.f.Indent,2) } if !cont { b.b.WriteString(indLvl) } } b.b.WriteByte('{') if b.pretty { b.b.WriteByte('\n') } b.writeField(erdata.FieldNameError,coll.ErrStr, indObj,true,false, false) if b.f.HasAddTypes() { types := coll.Types b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } if len(types) == 1 { b.writeField(erdata.FieldNameType, types[0].String(), indObj, true, false,false) } else { b.writeOpen(erdata.FieldNameTypes, indObj, '[', b.pretty) for i, t := range coll.TypesStr { if b.pretty { b.b.WriteString(ind2Obj) } b.b.WriteByte('"') b.b.WriteString(t) b.b.WriteByte('"') if i < len(coll.Types)-1 { b.b.WriteByte(',') } if b.pretty { b.b.WriteByte('\n') } } b.writeClose(indObj, ']') } } fields := coll.Fields() if len(fields) > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } fields.Sort() b.writeOpen(erdata.FieldNameFields, indObj, '{', b.pretty) var n = 0 for _, f := range fields { p := b.b.Len() if n > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } } b.writeFieldName(f.Name(),ind2Obj) if !b.writeFieldValue(f,level) { b.b.Truncate(p) continue } n++ } if n > 0 && b.pretty { b.b.WriteByte('\n') } b.writeClose(indObj,'}') } causes := coll.Causes if len (causes) > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } if len(causes) == 1 { b.writeOpen(erdata.FieldNameCause, indObj, 0, false) b.write(&causes[0], true,level+1) } else if len(causes) > 1 { b.writeOpen(erdata.FieldNameCauses, indObj, '[', b.pretty) for i, ce := range causes { if i > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } } b.write(&ce, false,level+1) } if b.pretty { b.b.WriteByte('\n') } b.writeClose(indObj, ']') } } if coll.Trace != nil { p := b.b.Len() b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } b.writeFieldName(erdata.FieldNameTrace,indObj) if b.encoder == nil { b.encoder = json.NewEncoder(b.b) } if b.pretty { b.encoder.SetIndent(strings.Repeat(b.f.Indent,level+1),b.f.Indent) } if er := b.encoder.Encode(coll.Trace.Frames()); er != nil { b.b.Truncate(p) } } if b.pretty { b.b.WriteByte('\n') } b.writeClose(indLvl,'}') d= true } func (b *jsonBuilder) writeOpen(name, indent string, object byte,nl bool) { b.writeFieldName(name,indent) if object != 0 { b.b.WriteByte(object) } if nl { b.b.WriteByte('\n') } } func (b *jsonBuilder) writeClose(indent string, object byte) { if b.pretty { b.b.WriteString(indent) } b.b.WriteByte(object) } func (b *jsonBuilder) writeFieldName (name, indent string) { if b.pretty { b.b.WriteString(indent) } cjson.WriteString(b.b,name,true) b.b.WriteString(b.sep) } func (b *jsonBuilder) writeField (name, value, indent string, quote, coma, nl bool) { b.writeFieldName(name,indent) if quote { b.b.WriteByte('"') } b.b.WriteString(value) if quote { b.b.WriteByte('"') } if coma { b.b.WriteByte(',') } if b.pretty && nl { b.b.WriteByte('\n') } } // writeFieldValue // 📋 TODO ver la posibilidad de esta logica encapsularla en los objetos field ya que se hara frecuente la repetición // de estea logica func (b *jsonBuilder) writeFieldValue (field flds.Field, level int) bool { switch field.Kind() { case flds.KindFloat: v := field.ValueFloat() if v == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteFloat(b.b,v,64,b.f.HasUseQuote()) return true case flds.KindInt: v := field.ValueInt() if v == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteInt(b.b,int64(v),b.f.HasUseQuote()) return true case flds.KindString: v := field.ValueString() if len(v) == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteString(b.b,v,b.f.HasEscapeHTML()) return true case flds.KindTime: v := field.ValueTime() if v.IsZero() && b.f.HasIgnoreDefaults(){ return false } cjson.WriteTime(b.b,v,b.f.TimeLayout,true) return true case flds.KindDuration: v := field.ValueDuration() if v == 0 && b.f.HasIgnoreDefaults(){ return false } cjson.WriteInt(b.b,int64(v),b.f.HasUseQuote()) return true case flds.KindBool: v := field.ValueBool() if !v && b.f.HasIgnoreDefaults() { return false } cjson.WriteBool(b.b,v,b.f.HasUseQuote()) return true case flds.KindBinary: v := field.ValueBinary() if v == nil && b.f.HasIgnoreDefaults() { return false } if er := cjson.WriteBase64(b.b,v); er != nil { return false } return true default: a := field.Value() if a == nil { if b.f.HasIgnoreDefaults() { return false } b.b.WOQ(true,'"',cjson.G.NullValue) return true } if b.encoder == nil { b.encoder = json.NewEncoder(b.b) b.encoder.SetEscapeHTML(b.f.HasEscapeHTML()) } if b.pretty { b.encoder.SetIndent(strings.Repeat(b.f.Indent,level+3),b.f.Indent) } if er := b.encoder.Encode(a); er != nil { return false } return true } } func (b *jsonBuilder) writeFormatError (err error) { b.b.Reset() b.b.WriteByte('{') b.writeFieldName("json-encoder-error","") cjson.WriteString(b.b,err.Error(),b.f.HasEscapeHTML()) b.b.WriteByte('}') } func (b *jsonBuilder) reset () { b.b.Reset() b.encoder = nil b.invalid = nil }