197 lines
5.0 KiB
Go
197 lines
5.0 KiB
Go
|
package icalendar
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"github.com/dolanor/caldav-go/icalendar/properties"
|
||
|
"github.com/dolanor/caldav-go/utils"
|
||
|
"log"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
Newline = "\r\n"
|
||
|
)
|
||
|
|
||
|
var _ = log.Print
|
||
|
|
||
|
type encoder func(reflect.Value) (string, error)
|
||
|
|
||
|
func tagAndJoinValue(v reflect.Value, in []string) (string, error) {
|
||
|
if tag, err := extractTagFromValue(v); err != nil {
|
||
|
return "", utils.NewError(tagAndJoinValue, "unable to extract tag from value", v, err)
|
||
|
} else {
|
||
|
var out []string
|
||
|
out = append(out, properties.MarshalProperty(properties.NewProperty("begin", tag)))
|
||
|
out = append(out, in...)
|
||
|
out = append(out, properties.MarshalProperty(properties.NewProperty("end", tag)))
|
||
|
return strings.Join(out, Newline), nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func marshalCollection(v reflect.Value) (string, error) {
|
||
|
|
||
|
var out []string
|
||
|
|
||
|
for i, n := 0, v.Len(); i < n; i++ {
|
||
|
vi := v.Index(i).Interface()
|
||
|
if encoded, err := Marshal(vi); err != nil {
|
||
|
msg := fmt.Sprintf("unable to encode interface at index %d", i)
|
||
|
return "", utils.NewError(marshalCollection, msg, vi, err)
|
||
|
} else if encoded != "" {
|
||
|
out = append(out, encoded)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return strings.Join(out, Newline), nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func marshalStruct(v reflect.Value) (string, error) {
|
||
|
|
||
|
var out []string
|
||
|
|
||
|
// iterate over all fields
|
||
|
vtype := v.Type()
|
||
|
n := vtype.NumField()
|
||
|
|
||
|
for i := 0; i < n; i++ {
|
||
|
|
||
|
// keep a reference to the field value and definition
|
||
|
fv := v.Field(i)
|
||
|
fs := vtype.Field(i)
|
||
|
|
||
|
// use the field definition to extract out property defaults
|
||
|
p := properties.PropertyFromStructField(fs)
|
||
|
if p == nil {
|
||
|
continue // skip explicitly ignored fields and private members
|
||
|
}
|
||
|
|
||
|
fi := fv.Interface()
|
||
|
|
||
|
// some fields are not properties, but actually nested objects.
|
||
|
// detect those early using the property and object encoder...
|
||
|
if _, ok := fi.(properties.CanEncodeValue); !ok && !isInvalidOrEmptyValue(fv) {
|
||
|
if encoded, err := encode(fv, objectEncoder); err != nil {
|
||
|
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
|
||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||
|
} else if encoded != "" {
|
||
|
// encoding worked! no need to process as a property
|
||
|
out = append(out, encoded)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// now check to see if the field value overrides the defaults...
|
||
|
if !isInvalidOrEmptyValue(fv) {
|
||
|
// first, check the field value interface for overrides...
|
||
|
if overrides, err := properties.PropertyFromInterface(fi); err != nil {
|
||
|
msg := fmt.Sprintf("field %s failed validation", fs.Name)
|
||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||
|
} else if p.Merge(overrides); p.Value == "" {
|
||
|
// then, if we couldn't find an override from the interface,
|
||
|
// try the simple string encoder...
|
||
|
if p.Value, err = stringEncoder(fv); err != nil {
|
||
|
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
|
||
|
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// make sure we have a value by this point
|
||
|
if !p.HasNameAndValue() {
|
||
|
if p.OmitEmpty {
|
||
|
continue
|
||
|
} else if p.DefaultValue != "" {
|
||
|
p.Value = p.DefaultValue
|
||
|
} else if p.Required {
|
||
|
msg := fmt.Sprintf("missing value for required field %s", fs.Name)
|
||
|
return "", utils.NewError(Marshal, msg, v.Interface(), nil)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// encode in the property
|
||
|
out = append(out, properties.MarshalProperty(p))
|
||
|
|
||
|
}
|
||
|
|
||
|
// wrap the fields in the enclosing struct tags
|
||
|
return tagAndJoinValue(v, out)
|
||
|
|
||
|
}
|
||
|
|
||
|
func objectEncoder(v reflect.Value) (string, error) {
|
||
|
|
||
|
// decompose the value into its interface parts
|
||
|
v = dereferencePointerValue(v)
|
||
|
|
||
|
// encode the value based off of its type
|
||
|
switch v.Kind() {
|
||
|
case reflect.Slice:
|
||
|
fallthrough
|
||
|
case reflect.Array:
|
||
|
return marshalCollection(v)
|
||
|
case reflect.Struct:
|
||
|
return marshalStruct(v)
|
||
|
}
|
||
|
|
||
|
return "", nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func stringEncoder(v reflect.Value) (string, error) {
|
||
|
return fmt.Sprintf("%v", v.Interface()), nil
|
||
|
}
|
||
|
|
||
|
func propertyEncoder(v reflect.Value) (string, error) {
|
||
|
|
||
|
vi := v.Interface()
|
||
|
if p, err := properties.PropertyFromInterface(vi); err != nil {
|
||
|
|
||
|
// return early if interface fails its own validation
|
||
|
return "", err
|
||
|
|
||
|
} else if p.HasNameAndValue() {
|
||
|
|
||
|
// if an interface encodes its own name and value, it's a property
|
||
|
return properties.MarshalProperty(p), nil
|
||
|
|
||
|
}
|
||
|
|
||
|
return "", nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func encode(v reflect.Value, encoders ...encoder) (string, error) {
|
||
|
|
||
|
for _, encode := range encoders {
|
||
|
if encoded, err := encode(v); err != nil {
|
||
|
return "", err
|
||
|
} else if encoded != "" {
|
||
|
return encoded, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return "", nil
|
||
|
|
||
|
}
|
||
|
|
||
|
// converts an iCalendar component into its string representation
|
||
|
func Marshal(target interface{}) (string, error) {
|
||
|
|
||
|
// don't do anything with invalid interfaces
|
||
|
v := reflect.ValueOf(target)
|
||
|
if isInvalidOrEmptyValue(v) {
|
||
|
return "", utils.NewError(Marshal, "unable to marshal empty or invalid values", target, nil)
|
||
|
}
|
||
|
|
||
|
if encoded, err := encode(v, propertyEncoder, objectEncoder, stringEncoder); err != nil {
|
||
|
return "", err
|
||
|
} else if encoded == "" {
|
||
|
return "", utils.NewError(Marshal, "unable to encode interface, all methods exhausted", v.Interface(), nil)
|
||
|
} else {
|
||
|
return encoded, nil
|
||
|
}
|
||
|
|
||
|
}
|