Go-Jacl
This module implements the base and the extended specifications of the Jacl configuration language.
Change Log
0.2.0 (2019-07-06)
0.1.0 (2019-06-30)
Installation
Requirements:
- Go 1.12 or better (not tried with versions below).
The following should be enough to install it:
go get github.com/yuce/go-jacl
Jacl Specification
See the Jacl configuration language for information about the configuration language.
Usage
Go-Jacl has a single function, jacl.Unmarshal
, to decode configuration from text into a map[string]interface{}
or a pointer to a struct.
Example:
text := `
// Sample configuration
bind: "https://01.pilosa.local:10101"
data-dir: "/tmp/data"
cluster: {
coordinator: true
}
tls: {
certificate: "pilosa.local.crt"
key: "pilosa.local.key"
skip-verify: true
}
gossip: {
seeds: [
"01.pilosa.local:15000"
"02.pilosa.local:15000"
]
port: 15000
key: "pilosa.local.gossip32"
}
`
// Decode to a map
config := map[string]interface{}{}
err := jacl.Unmarshal(text, &config)
if err != nil {
// handle the error
}
// Decode to a struct
type ClusterConfig struct {
Coordinator bool `jacl:"coordinator"`
SomeLegacyField string `jacl:"-"` // This field is skipped.
}
type TLSConfig struct {
CertificatePath string `jacl:"certificate"`
KeyPath string `jacl:"key"`
SkipVerify bool `jacl:"skip-verify"`
}
type GossipConfig struct {
Seeds []string `jacl:"seeds"`
Port int `jacl:"port"`
KeyPath string `jacl:"key"`
}
type Config struct {
DataDir string `jacl:"data-dir"`
Bind string `jacl:"bind"`
Cluster ClusterConfig `jacl:"cluster"`
TLS TLSConfig `jacl:"tls"`
Gossip GossipConfig `jacl:"gossip"`
}
config := Config{}
err := jacl.Unmarshal(text, &config)
if err != nil {
// handle the error
}
Decoding Into Structs
When decoding into structs:
- Only exported fields of the struct are considered.
- All fields of the struct must have corresponding values in the configuration, otherwise an error is returned.
- The field name must match the property/map key, unless
jacl:"KEY_NAME"
used in the field definition. In that case the configuration key KEY_NAME
is matched to the corresponding field.
- Use
jacl:"-"
in order to skip a field.
Supported Go Data Types
The following are the Go data types which are mapped from their Jacl counterparts. Note that trying to unmarshal to a field with a different type (e.g., a signed integer to uint
vice versa, or a float to int
) returns an error:
Jacl Type |
Go Type |
Allowed Field Types |
String |
string |
string |
Unsigned integer |
uint64 |
uint, uint8, uint16, uint32, uint64 |
Signed integer |
int64 |
int, int8, int16, int32, int64 |
Float |
float64 |
float32, float64 |
Boolean |
bool |
bool |
Array |
[]interface{} |
[]interface{}, []T |
Map |
map[string]interface{} |
map[string]interface{}, map[string]T |
In the table above T
is one of bool
, string
, int
, int8
, int16
, int32
, int64
, uint
, uint8
, uint16
, uint32
, uint64
, float32
, float64
.
Field Underflow/Overflow
If unmarshalling to a field underflows or overflows the chosen data type, then an error is returned:
type Config struct {
Number int8
}
config := Config{}
err := jacl.Unmarshal("Number: 128", &config)
err
above is not nil
, since 128 is bigger than math.MaxInt8.
Default Struct Values
Go-Jacl requires every field of a struct to be set on unmarshal unless a field is skipped with jacl:"-"
. So, if a property is missing in the configuration jacl.Unmarshal
would return an error.
Consider the following struct:
type C struct {
F1 string
F2 int
}
In order to have defaults for fields F1
and F2
, you can pass a map with defaults to jacl.Unmarshal
:
defaults := map[string]interface{}{
"F1": "default string",
"F2": 54,
}
text := `
F1: "modified string"
`
err := jacl.Unmarshal(text, &defaults)
if err != nil {
// handle the error
}
The defaults
map contains the following values after unmarshalling:
map[string]interface{}{
"F1": "modified string",
"F2": int64(54),
}
Since the properties for all fields are set, we can pass that map to jacl.UnmarshalStruct
:
config := C{}
err = jacl.UnmarshalStruct(defaults, &config)
if err != nil {
t.Fatal(err)
}
The value of config
is:
C{
F1: "modified string",
F2: 54,
}
Unmarshaling From Multiple Texts
Suppose we separated our configuration into multiple files since there are lots of properties to be set. Instead of having separate structs for each file, we want to have a single struct. We can use the same jack.UnmarshalStruct
technique in the previous section to accomplish that.
This is the sample struct:
type C struct {
F1 string
F2 string
// ...
F9 string
}
These are the contents of the configuration files:
texts := []string{
`F1: "field 1"`,
`F2: "field 2"`,
// ...
`F9: "field 9"`,
}
We use the same map to unmarshal each text:
props := map[string]interface{}{}
for _, text := range texts {
err := jacl.Unmarshal(text, &props)
if err != nil {
// handle the error
}
}
Finally, unmarshal the map to a struct:
config := C{}
err := jacl.UnmarshalStruct(props, &config)
if err != nil {
// handle the error
}
TODO
- Maps of typed slices and slices of typed maps are not yet supported when unmarshalling struct fields. E.g.,
Field1 []map[string]interface{}
is OK, but Field2 []map[string]int
is not supported yet.
License
Copyright (c) 2019-2021 Yuce Tekol. Licensed under MIT.