mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-15 13:19:53 +05:00
json.UnmarshalError happens only when we pass a non-nil pointer, which is not the case for us. Usage of go vet helps us make sure it also won't be happening in the future
417 lines
9.2 KiB
Go
417 lines
9.2 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/cgrates/cgrates/utils"
|
|
)
|
|
|
|
// NewRjReader creates a new rjReader from a io.Reader
|
|
func NewRjReader(rdr io.Reader) (r *RjReader, err error) {
|
|
var b []byte
|
|
b, err = io.ReadAll(rdr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return NewRjReaderFromBytes(b), nil
|
|
}
|
|
|
|
// NewRjReaderFromBytes creates a new rjReader from a slice of bytes
|
|
func NewRjReaderFromBytes(b []byte) *RjReader {
|
|
return &RjReader{buf: b}
|
|
}
|
|
|
|
// isNewLine check if byte is new line
|
|
func isNewLine(c byte) bool {
|
|
return c == '\n' || c == '\r'
|
|
}
|
|
|
|
// isWhiteSpace check if byte is white space
|
|
func isWhiteSpace(c byte) bool {
|
|
return c == ' ' || c == '\t' || isNewLine(c) || c == 0
|
|
}
|
|
|
|
// ReadEnv reads the enviorment variable
|
|
func ReadEnv(key string) (string, error) { //it shod print a warning not a error
|
|
if env := os.Getenv(key); env != "" {
|
|
return env, nil
|
|
}
|
|
return "", utils.ErrEnvNotFound(key)
|
|
}
|
|
|
|
// isAlfanum check if byte is number or letter
|
|
func isAlfanum(bit byte) bool {
|
|
return (bit >= 'a' && bit <= 'z') ||
|
|
(bit >= 'A' && bit <= 'Z') ||
|
|
(bit >= '0' && bit <= '9')
|
|
}
|
|
|
|
// RjReader structure that implements io.Reader to read json files ignoring C style comments and replacing *env:
|
|
type RjReader struct {
|
|
buf []byte
|
|
isInString bool // ignore character in strings
|
|
indx int // used to parse the buffer
|
|
envOff bool
|
|
}
|
|
|
|
// Read implementation
|
|
func (rjr *RjReader) Read(p []byte) (n int, err error) {
|
|
for n = range p {
|
|
p[n], err = rjr.ReadByte()
|
|
if !rjr.envOff &&
|
|
p[n] == '*' &&
|
|
rjr.checkMeta() {
|
|
if err = rjr.replaceEnv(rjr.indx - 1); err != nil {
|
|
return
|
|
}
|
|
p[n] = rjr.buf[rjr.indx-1] // replace with first value
|
|
}
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
n++ //because it starts from 0
|
|
return
|
|
}
|
|
|
|
// Close implementation
|
|
func (rjr *RjReader) Close() error {
|
|
rjr.buf = nil
|
|
return nil
|
|
}
|
|
|
|
// ReadByte implementation
|
|
func (rjr *RjReader) ReadByte() (bit byte, err error) {
|
|
if rjr.isInString { //ignore commas in strings
|
|
return rjr.ReadByteWC()
|
|
}
|
|
bit, err = rjr.ReadByteWC()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if bit == utils.CSVSep {
|
|
var bit2 byte
|
|
bit2, err = rjr.PeekByteWC()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if bit2 == ']' || bit2 == '}' {
|
|
return rjr.ReadByteWC()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// UnreadByte implementation
|
|
func (rjr *RjReader) UnreadByte() (err error) {
|
|
if rjr.indx <= 0 {
|
|
return bufio.ErrInvalidUnreadByte
|
|
}
|
|
rjr.indx--
|
|
return
|
|
}
|
|
|
|
// returns true if the file was parsed completly
|
|
func (rjr *RjReader) isEndOfFile() bool {
|
|
return rjr.indx >= len(rjr.buf)
|
|
}
|
|
|
|
// consumeComent consumes the comment based on the peeked byte
|
|
func (rjr *RjReader) consumeComent(pkbit byte) (bool, error) {
|
|
switch pkbit {
|
|
case '/':
|
|
for !rjr.isEndOfFile() {
|
|
bit := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if isNewLine(bit) { //read until newline or EOF
|
|
return true, nil
|
|
}
|
|
}
|
|
return true, io.EOF
|
|
case '*':
|
|
rjr.indx += 3 // increase to ignore peeked bytes
|
|
for !rjr.isEndOfFile() {
|
|
if rjr.buf[rjr.indx] == '/' &&
|
|
rjr.buf[rjr.indx-1] == '*' {
|
|
rjr.indx++
|
|
return true, nil
|
|
}
|
|
rjr.indx++
|
|
}
|
|
return true, utils.ErrJsonIncompleteComment
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// readFirstNonWhiteSpace reads first non white space byte
|
|
func (rjr *RjReader) readFirstNonWhiteSpace() (bit byte, err error) {
|
|
for !rjr.isEndOfFile() {
|
|
bit = rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if !isWhiteSpace(bit) {
|
|
return
|
|
}
|
|
}
|
|
return 0, io.EOF
|
|
}
|
|
|
|
// ReadByteWC reads next byte skiping the comments
|
|
func (rjr *RjReader) ReadByteWC() (bit byte, err error) {
|
|
if rjr.isEndOfFile() {
|
|
return 0, io.EOF
|
|
}
|
|
if rjr.isInString {
|
|
bit = rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
} else if bit, err = rjr.readFirstNonWhiteSpace(); err != nil {
|
|
return
|
|
}
|
|
if bit == '"' {
|
|
rjr.isInString = !rjr.isInString
|
|
return
|
|
}
|
|
if !rjr.isInString && !rjr.isEndOfFile() && bit == '/' {
|
|
var pkbit byte
|
|
var isComment bool
|
|
pkbit = rjr.buf[rjr.indx]
|
|
isComment, err = rjr.consumeComent(pkbit)
|
|
if err != nil && err != io.EOF {
|
|
return
|
|
}
|
|
if isComment {
|
|
return rjr.ReadByteWC()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// PeekByteWC peeks next byte skiping the comments
|
|
func (rjr *RjReader) PeekByteWC() (bit byte, err error) {
|
|
for !rjr.isEndOfFile() {
|
|
bit = rjr.buf[rjr.indx]
|
|
if !rjr.isInString && rjr.indx+1 < len(rjr.buf) && bit == '/' { //try consume comment
|
|
var pkbit byte
|
|
var isComment bool
|
|
pkbit = rjr.buf[rjr.indx+1]
|
|
isComment, err = rjr.consumeComent(pkbit)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if isComment {
|
|
return rjr.PeekByteWC()
|
|
}
|
|
return
|
|
}
|
|
if !isWhiteSpace(bit) {
|
|
return
|
|
}
|
|
rjr.indx++
|
|
}
|
|
return 0, io.EOF
|
|
}
|
|
|
|
// checkMeta check if char mach with next char from MetaEnv if not reset the counting
|
|
func (rjr *RjReader) checkMeta() bool {
|
|
return rjr.indx-1+len(utils.MetaEnv) < len(rjr.buf) &&
|
|
utils.MetaEnv == string(rjr.buf[rjr.indx-1:rjr.indx-1+len(utils.MetaEnv)])
|
|
}
|
|
|
|
// readEnvName reads the enviorment key
|
|
func (rjr *RjReader) readEnvName(indx int) (name []byte, endindx int) { //0 if not set
|
|
for indx < len(rjr.buf) { //read byte by byte
|
|
bit := rjr.buf[indx]
|
|
if !isAlfanum(bit) && bit != '_' { //[a-zA-Z_]+[a-zA-Z0-9_]*
|
|
return name, indx
|
|
}
|
|
name = append(name, bit)
|
|
indx++
|
|
}
|
|
return name, indx
|
|
}
|
|
|
|
// replaceEnv replaces the EnvMeta and enviorment key with enviorment variable value in specific buffer
|
|
func (rjr *RjReader) replaceEnv(startEnv int) error {
|
|
midEnv := len(utils.MetaEnv)
|
|
key, endEnv := rjr.readEnvName(startEnv + midEnv)
|
|
value, err := ReadEnv(string(key))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rjr.buf = append(rjr.buf[:startEnv], append([]byte(value), rjr.buf[endEnv:]...)...) // replace *env:ENV_STUFF with ENV_VALUE
|
|
return nil
|
|
}
|
|
|
|
// HandleJSONError warning: needs to read file again
|
|
func (rjr *RjReader) HandleJSONError(err error) error {
|
|
var offset int64
|
|
switch realErr := err.(type) {
|
|
case nil:
|
|
return nil
|
|
case *json.SyntaxError:
|
|
offset = realErr.Offset
|
|
case *json.UnmarshalTypeError:
|
|
offset = realErr.Offset
|
|
default:
|
|
return err
|
|
}
|
|
if offset == 0 {
|
|
return fmt.Errorf("%s at line 0 around position 0", err.Error())
|
|
}
|
|
rjr.indx = 0
|
|
|
|
line, character := rjr.getJSONOffsetLine(offset)
|
|
return fmt.Errorf("%s around line %v and position %v\n line: %q", err.Error(), line, character,
|
|
strings.Split(string(rjr.buf), "\n")[line-1])
|
|
}
|
|
|
|
func (rjr *RjReader) getJSONOffsetLine(offset int64) (line, character int64) {
|
|
line = 1 // start line counting from 1
|
|
var lastChar byte
|
|
|
|
var i int64 = 0
|
|
readString := func() error {
|
|
for i < offset {
|
|
if rjr.isEndOfFile() {
|
|
return io.EOF
|
|
}
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
i++
|
|
if isNewLine(b) {
|
|
line++
|
|
character = 0
|
|
} else {
|
|
character++
|
|
}
|
|
if b == '"' {
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
readLineComment := func() error {
|
|
for !rjr.isEndOfFile() {
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if isNewLine(b) {
|
|
line++
|
|
character = 0
|
|
return nil
|
|
}
|
|
character++
|
|
}
|
|
return io.EOF
|
|
}
|
|
|
|
readComment := func() error {
|
|
for !rjr.isEndOfFile() {
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if isNewLine(b) {
|
|
line++
|
|
character = 0
|
|
} else {
|
|
character++
|
|
}
|
|
if b == '*' {
|
|
if rjr.isEndOfFile() {
|
|
return io.EOF
|
|
}
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if b == '/' {
|
|
character++
|
|
return nil
|
|
}
|
|
// Unread byte return error only if rjr.indx is small or equal to 0
|
|
// The value is greater than 0 so it's safe to not check the error
|
|
rjr.UnreadByte()
|
|
}
|
|
}
|
|
return io.EOF
|
|
}
|
|
for i < offset { // handle the parsing
|
|
if rjr.isEndOfFile() {
|
|
return
|
|
}
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
character++
|
|
if isNewLine(b) {
|
|
line++
|
|
character = 0
|
|
}
|
|
if (b == ']' || b == '}') && lastChar == utils.CSVSep {
|
|
i-- //ignore utils.CSVSep if is followed by ] or }
|
|
}
|
|
if !isWhiteSpace(b) {
|
|
i++
|
|
lastChar = b
|
|
}
|
|
if b == '"' { // read "" value
|
|
rerr := readString()
|
|
if rerr != nil {
|
|
break
|
|
}
|
|
}
|
|
if b == '/' {
|
|
if rjr.isEndOfFile() {
|
|
return
|
|
}
|
|
b := rjr.buf[rjr.indx]
|
|
rjr.indx++
|
|
if b == '/' { // read //
|
|
character++
|
|
i--
|
|
rerr := readLineComment()
|
|
if rerr != nil {
|
|
break
|
|
}
|
|
} else if b == '*' { // read /*
|
|
character++
|
|
i--
|
|
rerr := readComment()
|
|
if rerr != nil {
|
|
break
|
|
}
|
|
} else {
|
|
// Unread byte return error only if rjr.indx is small or equal to 0
|
|
// The value is greater than 0 so it's safe to not check the error
|
|
rjr.UnreadByte()
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Decode loads the json config out of rjReader
|
|
func (rjr *RjReader) Decode(cfg any) (err error) {
|
|
if err = json.NewDecoder(rjr).Decode(cfg); err != nil {
|
|
return rjr.HandleJSONError(err)
|
|
}
|
|
return
|
|
}
|