| Index: service/rawdatastore/properties.go
|
| diff --git a/properties.go b/service/rawdatastore/properties.go
|
| similarity index 57%
|
| rename from properties.go
|
| rename to service/rawdatastore/properties.go
|
| index c7a7fb8c9f77dbad8cb3a3cc934ac35c3359540a..d6793df63fd62e9d289710d0ab26c54931b67b98 100644
|
| --- a/properties.go
|
| +++ b/service/rawdatastore/properties.go
|
| @@ -2,7 +2,7 @@
|
| // Use of this source code is governed by a BSD-style license that can be
|
| // found in the LICENSE file.
|
|
|
| -package gae
|
| +package rawdatastore
|
|
|
| import (
|
| "errors"
|
| @@ -10,26 +10,8 @@ import (
|
| "math"
|
| "reflect"
|
| "time"
|
| -)
|
| -
|
| -var (
|
| - // ErrDSMetaFieldUnset is returned from DSPropertyLoadSaver.{Get,Set}Meta
|
| - // implementations when the specified meta key isn't set on the struct at
|
| - // all.
|
| - ErrDSMetaFieldUnset = fmt.Errorf("gae: meta field unset")
|
| -)
|
|
|
| -var (
|
| - typeOfBSKey = reflect.TypeOf(BSKey(""))
|
| - typeOfBool = reflect.TypeOf(false)
|
| - typeOfByteSlice = reflect.TypeOf([]byte(nil))
|
| - typeOfDSByteString = reflect.TypeOf(DSByteString(nil))
|
| - typeOfDSGeoPoint = reflect.TypeOf(DSGeoPoint{})
|
| - typeOfDSKey = reflect.TypeOf((*DSKey)(nil)).Elem()
|
| - typeOfFloat64 = reflect.TypeOf(float64(0))
|
| - typeOfInt64 = reflect.TypeOf(int64(0))
|
| - typeOfString = reflect.TypeOf("")
|
| - typeOfTime = reflect.TypeOf(time.Time{})
|
| + "github.com/luci/gae/service/blobstore"
|
| )
|
|
|
| var (
|
| @@ -37,11 +19,13 @@ var (
|
| maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3)
|
| )
|
|
|
| +// IndexSetting indicates whether or not a Property should be indexed by the
|
| +// datastore.
|
| type IndexSetting bool
|
|
|
| +// ShouldIndex is the default, which is why it must assume the zero value,
|
| +// even though it's werid :(.
|
| const (
|
| - // ShouldIndex is the default, which is why it must assume the zero value,
|
| - // even though it's werid :(.
|
| ShouldIndex IndexSetting = false
|
| NoIndex IndexSetting = true
|
| )
|
| @@ -53,74 +37,74 @@ func (i IndexSetting) String() string {
|
| return "ShouldIndex"
|
| }
|
|
|
| -// DSProperty is a value plus an indicator of whether the value should be
|
| -// indexed. Name and Multiple are stored in the DSPropertyMap object.
|
| -type DSProperty struct {
|
| +// Property is a value plus an indicator of whether the value should be
|
| +// indexed. Name and Multiple are stored in the PropertyMap object.
|
| +type Property struct {
|
| value interface{}
|
| indexSetting IndexSetting
|
| - propType DSPropertyType
|
| + propType PropertyType
|
| }
|
|
|
| -// MkDSProperty makes a new indexed* DSProperty and returns it. If val is an
|
| +// MkProperty makes a new indexed* Property and returns it. If val is an
|
| // invalid value, this panics (so don't do it). If you want to handle the error
|
| // normally, use SetValue(..., ShouldIndex) instead.
|
| //
|
| // *indexed if val is not an unindexable type like []byte.
|
| -func MkDSProperty(val interface{}) DSProperty {
|
| - ret := DSProperty{}
|
| +func MkProperty(val interface{}) Property {
|
| + ret := Property{}
|
| if err := ret.SetValue(val, ShouldIndex); err != nil {
|
| panic(err)
|
| }
|
| return ret
|
| }
|
|
|
| -// MkDSPropertyNI makes a new DSProperty (with noindex set to true), and returns
|
| +// MkPropertyNI makes a new Property (with noindex set to true), and returns
|
| // it. If val is an invalid value, this panics (so don't do it). If you want to
|
| // handle the error normally, use SetValue(..., NoIndex) instead.
|
| -func MkDSPropertyNI(val interface{}) DSProperty {
|
| - ret := DSProperty{}
|
| +func MkPropertyNI(val interface{}) Property {
|
| + ret := Property{}
|
| if err := ret.SetValue(val, NoIndex); err != nil {
|
| panic(err)
|
| }
|
| return ret
|
| }
|
|
|
| -// DSPropertyConverter may be implemented by the pointer-to a struct field which
|
| -// is serialized by RawDatastore. Its ToDSProperty will be called on save, and
|
| -// it's FromDSProperty will be called on load (from datastore). The method may
|
| +// PropertyConverter may be implemented by the pointer-to a struct field which
|
| +// is serialized by RawDatastore. Its ToProperty will be called on save, and
|
| +// it's FromProperty will be called on load (from datastore). The method may
|
| // do arbitrary computation, and if it encounters an error, may return it. This
|
| -// error will be a fatal error (as defined by DSPropertyLoadSaver) for the
|
| +// error will be a fatal error (as defined by PropertyLoadSaver) for the
|
| // struct conversion.
|
| //
|
| // Example:
|
| // type Complex complex
|
| -// func (c *Complex) ToDSProperty() (ret DSProperty, err error) {
|
| +// func (c *Complex) ToProperty() (ret Property, err error) {
|
| // // something like:
|
| // err = ret.SetValue(fmt.Sprint(*c), true)
|
| // return
|
| // }
|
| -// func (c *Complex) FromDSProperty(p DSProperty) (err error) {
|
| +// func (c *Complex) FromProperty(p Property) (err error) {
|
| // ... load *c from p ...
|
| // }
|
| //
|
| // type MyStruct struct {
|
| // Complexity []Complex // acts like []complex, but can be serialized to DS
|
| // }
|
| -type DSPropertyConverter interface {
|
| +type PropertyConverter interface {
|
| // TODO(riannucci): Allow a convertable to return multiple values. This is
|
| // eminently doable (as long as the single-slice restriction is kept). It
|
| // could also cut down on the amount of reflection necessary when resolving
|
| // a path in a struct (in the struct loading routine in helper).
|
|
|
| - ToDSProperty() (DSProperty, error)
|
| - FromDSProperty(DSProperty) error
|
| + ToProperty() (Property, error)
|
| + FromProperty(Property) error
|
| }
|
|
|
| -// DSPropertyType is a single-byte representation of the type of data contained
|
| -// in a DSProperty. The specific values of this type information are chosen so
|
| +// PropertyType is a single-byte representation of the type of data contained
|
| +// in a Property. The specific values of this type information are chosen so
|
| // that the types sort according to the order of types as sorted by the
|
| // datastore.
|
| -type DSPropertyType byte
|
| +type PropertyType byte
|
|
|
| // These constants are in the order described by
|
| // https://cloud.google.com/appengine/docs/go/datastore/entities#Go_Value_type_ordering
|
| @@ -128,10 +112,10 @@ type DSPropertyType byte
|
| // NOTE: this enum can only occupy 7 bits, because we use the high bit to encode
|
| // indexed/non-indexed. See typData.WriteBinary.
|
| const (
|
| - DSPTNull DSPropertyType = iota
|
| - DSPTInt
|
| + PTNull PropertyType = iota
|
| + PTInt
|
|
|
| - // DSPTTime is a slight divergence from the way that datastore natively stores
|
| + // PTTime is a slight divergence from the way that datastore natively stores
|
| // time. In datastore, times and integers actually sort together
|
| // (apparently?). This is probably insane, and I don't want to add the
|
| // complexity of field 'meaning' as a sparate concept from the field's 'type'
|
| @@ -141,82 +125,82 @@ const (
|
| // production. My advice is to NOT DO THAT. If you really want this (and you
|
| // probably don't), you should take care of the time.Time <-> int64 conversion
|
| // in your app and just use a property type of int64 (consider using
|
| - // DSPropertyConverter).
|
| - DSPTTime
|
| + // PropertyConverter).
|
| + PTTime
|
|
|
| - // DSPTBoolFalse and True are also a slight divergence, but not a semantic
|
| + // PTBoolFalse and True are also a slight divergence, but not a semantic
|
| // one. IIUC, in datastore 'bool' is actually the type and the value is either
|
| // 0 or 1 (taking another byte to store). Since we have plenty of space in
|
| // this type byte, I just merge the value into the type for booleans. If this
|
| // becomes problematic, consider changing this to just pvBool, and then
|
| // encoding a 0 or 1 as a byte in the relevant marshalling routines.
|
| - DSPTBoolFalse
|
| - DSPTBoolTrue
|
| + PTBoolFalse
|
| + PTBoolTrue
|
|
|
| - DSPTBytes // []byte or datastore.ByteString
|
| - DSPTString // string or string noindex
|
| - DSPTFloat
|
| - DSPTGeoPoint
|
| - DSPTKey
|
| - DSPTBlobKey
|
| + PTBytes // []byte or datastore.ByteString
|
| + PTString // string or string noindex
|
| + PTFloat
|
| + PTGeoPoint
|
| + PTKey
|
| + PTBlobKey
|
|
|
| - DSPTUnknown
|
| + PTUnknown
|
| )
|
|
|
| -func (t DSPropertyType) String() string {
|
| +func (t PropertyType) String() string {
|
| switch t {
|
| - case DSPTNull:
|
| - return "DSPTNull"
|
| - case DSPTInt:
|
| - return "DSPTInt"
|
| - case DSPTTime:
|
| - return "DSPTTime"
|
| - case DSPTBoolFalse:
|
| - return "DSPTBoolFalse"
|
| - case DSPTBoolTrue:
|
| - return "DSPTBoolTrue"
|
| - case DSPTBytes:
|
| - return "DSPTBytes"
|
| - case DSPTString:
|
| - return "DSPTString"
|
| - case DSPTFloat:
|
| - return "DSPTFloat"
|
| - case DSPTGeoPoint:
|
| - return "DSPTGeoPoint"
|
| - case DSPTKey:
|
| - return "DSPTKey"
|
| - case DSPTBlobKey:
|
| - return "DSPTBlobKey"
|
| + case PTNull:
|
| + return "PTNull"
|
| + case PTInt:
|
| + return "PTInt"
|
| + case PTTime:
|
| + return "PTTime"
|
| + case PTBoolFalse:
|
| + return "PTBoolFalse"
|
| + case PTBoolTrue:
|
| + return "PTBoolTrue"
|
| + case PTBytes:
|
| + return "PTBytes"
|
| + case PTString:
|
| + return "PTString"
|
| + case PTFloat:
|
| + return "PTFloat"
|
| + case PTGeoPoint:
|
| + return "PTGeoPoint"
|
| + case PTKey:
|
| + return "PTKey"
|
| + case PTBlobKey:
|
| + return "PTBlobKey"
|
| default:
|
| - return fmt.Sprintf("DSPTUnknown(%02x)", byte(t))
|
| + return fmt.Sprintf("PTUnknown(%02x)", byte(t))
|
| }
|
| }
|
|
|
| -// DSPropertyTypeOf returns the DSPT* type of the given DSProperty-compatible
|
| +// PropertyTypeOf returns the PT* type of the given Property-compatible
|
| // value v. If checkValid is true, this method will also ensure that time.Time
|
| -// and DSGeoPoint have valid values.
|
| -func DSPropertyTypeOf(v interface{}, checkValid bool) (DSPropertyType, error) {
|
| +// and GeoPoint have valid values.
|
| +func PropertyTypeOf(v interface{}, checkValid bool) (PropertyType, error) {
|
| switch x := v.(type) {
|
| case nil:
|
| - return DSPTNull, nil
|
| + return PTNull, nil
|
| case int64:
|
| - return DSPTInt, nil
|
| + return PTInt, nil
|
| case float64:
|
| - return DSPTFloat, nil
|
| + return PTFloat, nil
|
| case bool:
|
| if x {
|
| - return DSPTBoolTrue, nil
|
| + return PTBoolTrue, nil
|
| }
|
| - return DSPTBoolFalse, nil
|
| - case []byte, DSByteString:
|
| - return DSPTBytes, nil
|
| - case BSKey:
|
| - return DSPTBlobKey, nil
|
| + return PTBoolFalse, nil
|
| + case []byte, ByteString:
|
| + return PTBytes, nil
|
| + case blobstore.Key:
|
| + return PTBlobKey, nil
|
| case string:
|
| - return DSPTString, nil
|
| - case DSKey:
|
| + return PTString, nil
|
| + case Key:
|
| // TODO(riannucci): Check key for validity in its own namespace?
|
| - return DSPTKey, nil
|
| + return PTKey, nil
|
| case time.Time:
|
| err := error(nil)
|
| if checkValid && (x.Before(minTime) || x.After(maxTime)) {
|
| @@ -225,22 +209,22 @@ func DSPropertyTypeOf(v interface{}, checkValid bool) (DSPropertyType, error) {
|
| if checkValid && x.Location() != time.UTC {
|
| err = fmt.Errorf("time value has wrong Location: %s", x.Location())
|
| }
|
| - return DSPTTime, err
|
| - case DSGeoPoint:
|
| + return PTTime, err
|
| + case GeoPoint:
|
| err := error(nil)
|
| if checkValid && !x.Valid() {
|
| err = errors.New("invalid GeoPoint value")
|
| }
|
| - return DSPTGeoPoint, err
|
| + return PTGeoPoint, err
|
| default:
|
| - return DSPTUnknown, fmt.Errorf("gae: DSProperty has bad type %T", v)
|
| + return PTUnknown, fmt.Errorf("gae: Property has bad type %T", v)
|
| }
|
| }
|
|
|
| -// DSUpconvertUnderlyingType takes an object o, and attempts to convert it to
|
| +// UpconvertUnderlyingType takes an object o, and attempts to convert it to
|
| // its native datastore-compatible type. e.g. int16 will convert to int64, and
|
| // `type Foo string` will convert to `string`.
|
| -func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, reflect.Type) {
|
| +func UpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, reflect.Type) {
|
| v := reflect.ValueOf(o)
|
| switch t.Kind() {
|
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
| @@ -258,13 +242,13 @@ func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, refl
|
| o = v.Float()
|
| t = typeOfFloat64
|
| case reflect.Slice:
|
| - if t != typeOfDSByteString && t.Elem().Kind() == reflect.Uint8 {
|
| + if t != typeOfByteString && t.Elem().Kind() == reflect.Uint8 {
|
| o = v.Bytes()
|
| t = typeOfByteSlice
|
| }
|
| case reflect.Struct:
|
| if t == typeOfTime {
|
| - // time in a DSProperty can only hold microseconds
|
| + // time in a Property can only hold microseconds
|
| o = v.Interface().(time.Time).Round(time.Microsecond)
|
| }
|
| }
|
| @@ -274,29 +258,29 @@ func DSUpconvertUnderlyingType(o interface{}, t reflect.Type) (interface{}, refl
|
| // Value returns the current value held by this property. It's guaranteed to
|
| // be a valid value type (i.e. `p.SetValue(p.Value(), true)` will never return
|
| // an error).
|
| -func (p DSProperty) Value() interface{} { return p.value }
|
| +func (p Property) Value() interface{} { return p.value }
|
|
|
| // IndexSetting says weather or not the datastore should create indicies for
|
| // this value.
|
| -func (p DSProperty) IndexSetting() IndexSetting { return p.indexSetting }
|
| +func (p Property) IndexSetting() IndexSetting { return p.indexSetting }
|
|
|
| -// Type is the DSPT* type of the data contained in Value().
|
| -func (p DSProperty) Type() DSPropertyType { return p.propType }
|
| +// Type is the PT* type of the data contained in Value().
|
| +func (p Property) Type() PropertyType { return p.propType }
|
|
|
| -// SetValue sets the Value field of a DSProperty, and ensures that its value
|
| +// SetValue sets the Value field of a Property, and ensures that its value
|
| // conforms to the permissible types. That way, you're guaranteed that if you
|
| -// have a DSProperty, its value is valid.
|
| +// have a Property, its value is valid.
|
| //
|
| // value is the property value. The valid types are:
|
| // - int64
|
| // - bool
|
| // - string
|
| // - float64
|
| -// - DSByteString
|
| -// - DSKey
|
| +// - ByteString
|
| +// - Key
|
| // - time.Time
|
| -// - BSKey
|
| -// - DSGeoPoint
|
| +// - blobstore.Key
|
| +// - GeoPoint
|
| // - []byte (up to 1 megabyte in length)
|
| // This set is smaller than the set of valid struct field types that the
|
| // datastore can load and save. A Property Value cannot be a slice (apart
|
| @@ -311,13 +295,13 @@ func (p DSProperty) Type() DSPropertyType { return p.propType }
|
| // Python's None but not directly representable by a Go struct. Loading
|
| // a nil-valued property into a struct will set that field to the zero
|
| // value.
|
| -func (p *DSProperty) SetValue(value interface{}, is IndexSetting) (err error) {
|
| +func (p *Property) SetValue(value interface{}, is IndexSetting) (err error) {
|
| t := reflect.Type(nil)
|
| - pt := DSPTNull
|
| + pt := PTNull
|
| if value != nil {
|
| t = reflect.TypeOf(value)
|
| - value, t = DSUpconvertUnderlyingType(value, t)
|
| - if pt, err = DSPropertyTypeOf(value, true); err != nil {
|
| + value, t = UpconvertUnderlyingType(value, t)
|
| + if pt, err = PropertyTypeOf(value, true); err != nil {
|
| return
|
| }
|
| }
|
| @@ -330,21 +314,21 @@ func (p *DSProperty) SetValue(value interface{}, is IndexSetting) (err error) {
|
| return
|
| }
|
|
|
| -// DSPropertyLoadSaver may be implemented by a user type, and RawDatastore will
|
| +// PropertyLoadSaver may be implemented by a user type, and RawDatastore will
|
| // use this interface to serialize the type instead of trying to automatically
|
| // create a serialization codec for it with helper.GetPLS.
|
| -type DSPropertyLoadSaver interface {
|
| +type PropertyLoadSaver interface {
|
| // Load takes the values from the given map and attempts to save them into
|
| - // the underlying object (usually a struct or a DSPropertyMap). If a fatal
|
| + // the underlying object (usually a struct or a PropertyMap). If a fatal
|
| // error occurs, it's returned via error. If non-fatal conversion errors
|
| - // occur, error will be a MultiError containing one or more ErrDSFieldMismatch
|
| + // occur, error will be a MultiError containing one or more ErrFieldMismatch
|
| // objects.
|
| - Load(DSPropertyMap) error
|
| + Load(PropertyMap) error
|
|
|
| - // Save returns the current property as a DSPropertyMap. if withMeta is true,
|
| - // then the DSPropertyMap contains all the metadata (e.g. '$meta' fields)
|
| - // which was held by this DSPropertyLoadSaver.
|
| - Save(withMeta bool) (DSPropertyMap, error)
|
| + // Save returns the current property as a PropertyMap. if withMeta is true,
|
| + // then the PropertyMap contains all the metadata (e.g. '$meta' fields)
|
| + // which was held by this PropertyLoadSaver.
|
| + Save(withMeta bool) (PropertyMap, error)
|
|
|
| // GetMeta will get information about the field which has the struct tag in
|
| // the form of `gae:"$<key>[,<value>]?"`.
|
| @@ -374,48 +358,48 @@ type DSPropertyLoadSaver interface {
|
| Problem() error
|
| }
|
|
|
| -// DSPropertyMap represents the contents of a datastore entity in a generic way.
|
| +// PropertyMap represents the contents of a datastore entity in a generic way.
|
| // It maps from property name to a list of property values which correspond to
|
| // that property name. It is the spiritual successor to PropertyList from the
|
| // original SDK.
|
| //
|
| -// DSPropertyMap may contain "meta" values, which are keyed with a '$' prefix.
|
| +// PropertyMap may contain "meta" values, which are keyed with a '$' prefix.
|
| // Technically the datastore allows arbitrary property names, but all of the
|
| // SDKs go out of their way to try to make all property names valid programming
|
| -// language tokens. Special values must correspond to a single DSProperty...
|
| +// language tokens. Special values must correspond to a single Property...
|
| // corresponding to 0 is equivalent to unset, and corresponding to >1 is an
|
| // error. So:
|
| //
|
| // {
|
| -// "$id": {MkDSProperty(1)}, // GetProperty("id") -> 1, nil
|
| -// "$foo": {}, // GetProperty("foo") -> nil, ErrDSMetaFieldUnset
|
| -// // GetProperty("bar") -> nil, ErrDSMetaFieldUnset
|
| +// "$id": {MkProperty(1)}, // GetProperty("id") -> 1, nil
|
| +// "$foo": {}, // GetProperty("foo") -> nil, ErrMetaFieldUnset
|
| +// // GetProperty("bar") -> nil, ErrMetaFieldUnset
|
| // "$meep": {
|
| -// MkDSProperty("hi"),
|
| -// MkDSProperty("there")}, // GetProperty("meep") -> nil, error!
|
| +// MkProperty("hi"),
|
| +// MkProperty("there")}, // GetProperty("meep") -> nil, error!
|
| // }
|
| //
|
| // Additionally, Save returns a copy of the map with the meta keys omitted (e.g.
|
| // these keys are not going to be serialized to the datastore).
|
| -type DSPropertyMap map[string][]DSProperty
|
| +type PropertyMap map[string][]Property
|
|
|
| -var _ DSPropertyLoadSaver = DSPropertyMap(nil)
|
| +var _ PropertyLoadSaver = PropertyMap(nil)
|
|
|
| -// Load implements DSPropertyLoadSaver.Load
|
| -func (pm DSPropertyMap) Load(props DSPropertyMap) error {
|
| +// Load implements PropertyLoadSaver.Load
|
| +func (pm PropertyMap) Load(props PropertyMap) error {
|
| for k, v := range props {
|
| pm[k] = append(pm[k], v...)
|
| }
|
| return nil
|
| }
|
|
|
| -// Save implements DSPropertyLoadSaver.Save by returning a copy of the
|
| +// Save implements PropertyLoadSaver.Save by returning a copy of the
|
| // current map data.
|
| -func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) {
|
| +func (pm PropertyMap) Save(withMeta bool) (PropertyMap, error) {
|
| if len(pm) == 0 {
|
| - return DSPropertyMap{}, nil
|
| + return PropertyMap{}, nil
|
| }
|
| - ret := make(DSPropertyMap, len(pm))
|
| + ret := make(PropertyMap, len(pm))
|
| for k, v := range pm {
|
| if withMeta || len(k) == 0 || k[0] != '$' {
|
| ret[k] = append(ret[k], v...)
|
| @@ -424,10 +408,13 @@ func (pm DSPropertyMap) Save(withMeta bool) (DSPropertyMap, error) {
|
| return ret, nil
|
| }
|
|
|
| -func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) {
|
| +// GetMeta implements PropertyLoadSaver.GetMeta, and returns the current value
|
| +// associated with the metadata key. It may return ErrMetaFieldUnset if the
|
| +// key doesn't exist.
|
| +func (pm PropertyMap) GetMeta(key string) (interface{}, error) {
|
| v, ok := pm["$"+key]
|
| if !ok || len(v) == 0 {
|
| - return nil, ErrDSMetaFieldUnset
|
| + return nil, ErrMetaFieldUnset
|
| }
|
| if len(v) > 1 {
|
| return nil, errors.New("gae: too many values for Meta key")
|
| @@ -435,15 +422,18 @@ func (pm DSPropertyMap) GetMeta(key string) (interface{}, error) {
|
| return v[0].Value(), nil
|
| }
|
|
|
| -func (pm DSPropertyMap) SetMeta(key string, val interface{}) error {
|
| - prop := DSProperty{}
|
| +// SetMeta implements PropertyLoadSaver.SetMeta. It will only return an error
|
| +// if `val` has an invalid type (e.g. not one supported by Property).
|
| +func (pm PropertyMap) SetMeta(key string, val interface{}) error {
|
| + prop := Property{}
|
| if err := prop.SetValue(val, NoIndex); err != nil {
|
| return err
|
| }
|
| - pm["$"+key] = []DSProperty{prop}
|
| + pm["$"+key] = []Property{prop}
|
| return nil
|
| }
|
|
|
| -func (pm DSPropertyMap) Problem() error {
|
| +// Problem implements PropertyLoadSaver.Problem. It ALWAYS returns nil.
|
| +func (pm PropertyMap) Problem() error {
|
| return nil
|
| }
|
|
|