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 } }