2019-10-22 16:36:54 +00:00
package config
import (
2019-10-25 18:12:38 +00:00
"fmt"
2019-10-22 19:55:43 +00:00
"github.com/pkg/errors"
2019-10-22 16:36:54 +00:00
"io/ioutil"
2019-10-25 18:12:38 +00:00
"github.com/santhosh-tekuri/jsonschema"
2019-10-22 16:36:54 +00:00
"gopkg.in/yaml.v2"
)
2019-10-29 01:23:57 +00:00
// Config is for top-level struct for config
2019-10-22 16:36:54 +00:00
type Config struct {
Telegram TelegramConfig ` yaml:":telegram" `
2019-10-29 01:23:57 +00:00
XMPP XMPPConfig ` yaml:":xmpp" `
2019-10-22 16:36:54 +00:00
}
2019-10-29 01:23:57 +00:00
// XMPPConfig is for :xmpp: subtree
type XMPPConfig struct {
2019-10-22 16:36:54 +00:00
Loglevel string ` yaml:":loglevel" `
Jid string ` yaml:":jid" `
Host string ` yaml:":host" `
Port string ` yaml:":port" `
Password string ` yaml:":password" `
Db string ` yaml:":db" `
}
2019-10-29 01:23:57 +00:00
// TelegramConfig is for :telegram: subtree
2019-10-22 16:36:54 +00:00
type TelegramConfig struct {
Loglevel string ` yaml:":loglevel" `
Content TelegramContentConfig ` yaml:":content" `
Verbosity uint8 ` yaml:":tdlib_verbosity" `
Tdlib TelegramTdlibConfig ` yaml:":tdlib" `
}
2019-10-29 01:23:57 +00:00
// TelegramContentConfig is for :content: subtree
2019-10-22 16:36:54 +00:00
type TelegramContentConfig struct {
Path string ` yaml:":path" `
Link string ` yaml:":link" `
Upload string ` yaml:":upload" `
}
2019-10-29 01:23:57 +00:00
// TelegramTdlibConfig is for :tdlib: subtree
2019-10-22 16:36:54 +00:00
type TelegramTdlibConfig struct {
2019-10-29 01:23:57 +00:00
Path string ` yaml:":lib_path" `
Logfile string ` yaml:":logfile" `
Client TelegramTdlibClientConfig ` yaml:":client" `
2019-10-22 16:36:54 +00:00
}
2019-10-29 01:23:57 +00:00
// TelegramTdlibClientConfig is for :client: subtree
2019-10-22 16:36:54 +00:00
type TelegramTdlibClientConfig struct {
2019-10-29 01:23:57 +00:00
APIID string ` yaml:":api_id" `
APIHash string ` yaml:":api_hash" `
2019-10-22 16:36:54 +00:00
DeviceModel string ` yaml:":device_model" `
ApplicationVersion string ` yaml:":application_version" `
UseChatInfoDatabase bool ` yaml:":use_chat_info_database" `
}
2019-10-29 01:23:57 +00:00
// ReadConfig reads the specified config file, validates it and returns a struct
func ReadConfig ( path string , schemaPath string ) ( Config , error ) {
2019-10-22 16:36:54 +00:00
var config Config
file , err := ioutil . ReadFile ( path )
if err != nil {
2019-10-22 19:55:43 +00:00
return config , errors . Wrap ( err , "Can't open config file" )
2019-10-22 16:36:54 +00:00
}
err = yaml . Unmarshal ( file , & config )
if err != nil {
2019-10-22 19:55:43 +00:00
return config , errors . Wrap ( err , "Error parsing config" )
2019-10-22 16:36:54 +00:00
}
2019-10-29 01:23:57 +00:00
err = validateConfig ( file , schemaPath )
2019-10-25 18:12:38 +00:00
if err != nil {
return config , errors . Wrap ( err , "Validation error" )
}
2019-10-22 19:55:43 +00:00
return config , nil
2019-10-22 16:36:54 +00:00
}
2019-10-25 18:12:38 +00:00
2019-10-29 01:23:57 +00:00
func validateConfig ( file [ ] byte , schemaPath string ) error {
schema , err := jsonschema . Compile ( schemaPath )
2019-10-25 18:12:38 +00:00
if err != nil {
return errors . Wrap ( err , "Corrupted JSON schema" )
}
2019-10-29 01:23:57 +00:00
var configGeneric interface { }
2019-10-25 18:12:38 +00:00
2019-10-29 01:23:57 +00:00
err = yaml . Unmarshal ( file , & configGeneric )
2019-10-25 18:12:38 +00:00
if err != nil {
return errors . Wrap ( err , "Error re-parsing config" )
}
2019-10-29 01:23:57 +00:00
configGeneric , err = convertToStringKeysRecursive ( configGeneric , "" )
2019-10-25 18:12:38 +00:00
if err != nil {
return errors . Wrap ( err , "Config conversion error" )
}
2019-10-29 01:23:57 +00:00
err = schema . ValidateInterface ( configGeneric )
2019-10-25 18:12:38 +00:00
if err != nil {
return errors . Wrap ( err , "Config validation error" )
}
return nil
}
// copied and adapted from https://github.com/docker/docker-ce/blob/de14285fad39e215ea9763b8b404a37686811b3f/components/cli/cli/compose/loader/loader.go#L330
func convertToStringKeysRecursive ( value interface { } , keyPrefix string ) ( interface { } , error ) {
if mapping , ok := value . ( map [ interface { } ] interface { } ) ; ok {
dict := make ( map [ string ] interface { } )
for key , entry := range mapping {
str , ok := key . ( string )
if ! ok {
return nil , formatInvalidKeyError ( keyPrefix , key )
}
var newKeyPrefix string
if keyPrefix == "" {
newKeyPrefix = str
} else {
newKeyPrefix = fmt . Sprintf ( "%s.%s" , keyPrefix , str )
}
convertedEntry , err := convertToStringKeysRecursive ( entry , newKeyPrefix )
if err != nil {
return nil , err
}
dict [ str ] = convertedEntry
}
return dict , nil
}
if list , ok := value . ( [ ] interface { } ) ; ok {
var convertedList [ ] interface { }
for index , entry := range list {
newKeyPrefix := fmt . Sprintf ( "%s[%d]" , keyPrefix , index )
convertedEntry , err := convertToStringKeysRecursive ( entry , newKeyPrefix )
if err != nil {
return nil , err
}
convertedList = append ( convertedList , convertedEntry )
}
return convertedList , nil
}
return value , nil
}
func formatInvalidKeyError ( keyPrefix string , key interface { } ) error {
var location string
if keyPrefix == "" {
location = "at top level"
} else {
location = fmt . Sprintf ( "in %s" , keyPrefix )
}
return errors . Errorf ( "Non-string key %s: %#v" , location , key )
}