// ------------------------------------------------------------------------ // 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 erjson import ( "codecs/cjson" "encoding/json" "errs/erdata" "fmt" "mems" "strings" ) type ( jsonBuilder struct { f *JSONEncoder b *mems.ABuffer encoder *json.Encoder sep string pretty bool invalid error } ) func newJsonBuilder (f *JSONEncoder) *jsonBuilder { sep := ":" if f.HasUseSpace() && f.HasShowPretty() { sep = ": " } return &jsonBuilder{ f : f, b : mems.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.layout.Indent, 1+level) indObj = strings.Repeat(b.f.layout.Indent, 2+level) ind2Obj = strings.Repeat(b.f.layout.Indent, 3+level) } else { indObj = b.f.layout.Indent ind2Obj = strings.Repeat(b.f.layout.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, ']') } } heads := coll.Fields.GetHeads() if len(heads) > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } coll.Fields.Sort() b.writeOpen(erdata.FieldNameFields, indObj, '{', b.pretty) var n = 0 for _, h := range heads { p := b.b.Len() if n > 0 { b.b.WriteByte(',') if b.pretty { b.b.WriteByte('\n') } } b.writeFieldName(h.Name,ind2Obj) if !b.writeFieldValue(coll.Fields, h,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.layout.Indent,level+1),b.f.layout.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') } } func (b *jsonBuilder) writeFieldValue (fields erdata.Fields, field *erdata.FieldHead, level int) bool { switch field.Kind { case erdata.FieldKindFloat: n := fields.GetFieldValueFloat(field) if n == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteFloat(b.b,n,64,b.f.HasUseQuote()) return true case erdata.FieldKindInt: n := fields.GetFieldValueInt(field) if n == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteInt(b.b,int64(n),b.f.HasUseQuote()) return true case erdata.FieldKindString: s := fields.GetFieldValueString(field) if len(s) == 0 && b.f.HasIgnoreDefaults() { return false } cjson.WriteString(b.b,s,b.f.HasEscapeHTML()) return true case erdata.FieldKindTime: d := fields.GetFieldValueDate(field) if d.IsZero() && b.f.HasIgnoreDefaults(){ return false } cjson.WriteDate(b.b,d,b.f.layout.TimeLayout,true) return true case erdata.FieldKindDate: d := fields.GetFieldValueDate(field) if d.IsZero() && b.f.HasIgnoreDefaults(){ return false } cjson.WriteDate(b.b,d,b.f.layout.DateLayout,true) return true case erdata.FieldKindBool: t := fields.GetFieldValueBool(field) if !t && b.f.HasIgnoreDefaults() { return false } cjson.WriteBool(b.b,t,b.f.HasUseQuote()) return true default: a := fields.GetFieldValueAny(field) 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.layout.Indent,level+3),b.f.layout.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-formatter-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 }