diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go b/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go new file mode 100644 index 00000000..318a2cae --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/calls.go @@ -0,0 +1,85 @@ +package locode + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/netmap" + "github.com/nspcc-dev/neofs-node/pkg/util/locode" + "github.com/pkg/errors" +) + +var errMissingLocode = errors.New("missing locode attribute") + +var errMissingRequiredAttr = errors.New("missing required attribute in DB record") + +// VerifyAndUpdate validates LOCODE attribute of n +// and adds a group of related attributes. +// +// If n does not contain LOCODE attribute or its value does not +// match the UN/LOCODE format, an error returns. +// +// New attributes are formed from the record of DB instance (Prm). +// If DB entry R was found w/o errors, then new attributes are: +// * CountryCode: R.CountryCode().String(); +// * Country: R.CountryName(); +// * CityCode: R.LocationCode().String(); +// * City: Record.LocationName(); +// * SubDivCode: R.SubDivCode(); +// * SubDiv: R.SubDivName(); +// * Continent: R.Continent().String(). +// +// LOCODE attribute remains untouched. +func (v *Validator) VerifyAndUpdate(n *netmap.NodeInfo) error { + mAttr := uniqueAttributes(n.Attributes()) + + attrLocode, ok := mAttr[attrKeyLocode] + if !ok { + return errMissingLocode + } + + lc, err := locode.FromString(attrLocode.Value()) + if err != nil { + return errors.Wrap(err, "invalid locode value") + } + + record, err := v.db.Get(lc) + if err != nil { + return errors.Wrap(err, "could not get locode record from DB") + } + + for attrKey, attrDesc := range v.mAttr { + attrVal := attrDesc.converter(record) + if attrVal == "" { + if !attrDesc.optional { + return errMissingRequiredAttr + } + + continue + } + + a := netmap.NewNodeAttribute() + a.SetKey(attrKey) + a.SetValue(attrVal) + + mAttr[attrKey] = a + } + + as := n.Attributes() + as = as[:0] + + for _, attr := range mAttr { + as = append(as, attr) + } + + n.SetAttributes(as...) + + return nil +} + +func uniqueAttributes(as []*netmap.NodeAttribute) map[string]*netmap.NodeAttribute { + mAttr := make(map[string]*netmap.NodeAttribute, len(as)) + + for _, attr := range as { + mAttr[attr.Key()] = attr + } + + return mAttr +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go b/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go new file mode 100644 index 00000000..5a5078ff --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/deps.go @@ -0,0 +1,59 @@ +package locode + +import ( + "github.com/nspcc-dev/neofs-node/pkg/util/locode" + locodedb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db" +) + +// Record is an interface of read-only +// NeoFS LOCODE database single entry. +type Record interface { + // Must return ISO 3166-1 alpha-2 + // country code. + // + // Must not return nil. + CountryCode() *locodedb.CountryCode + + // Must return English short country name + // officially used by the ISO 3166 + // Maintenance Agency (ISO 3166/MA). + CountryName() string + + // Must return UN/LOCODE 3-character code + // for the location (numerals 2-9 may also + // be used). + // + // Must not return nil. + LocationCode() *locodedb.LocationCode + + // Must return name of the location which + // have been allocated a UN/LOCODE without + // diacritic sign. + LocationName() string + + // Must return ISO 1-3 character alphabetic + // and/or numeric code for the administrative + // division of the country concerned. + SubDivCode() string + + // Must return subdivision name. + SubDivName() string + + // Must return existing continent where is + // the location. + // + // Must not return nil. + Continent() *locodedb.Continent +} + +// DB is an interface of read-only +// NeoFS LOCODE database. +type DB interface { + // Must find the record that corresponds to + // LOCODE and provides the Record interface. + // + // Must return an error if Record is nil. + // + // LOCODE is always non-nil. + Get(*locode.LOCODE) (Record, error) +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/util.go b/pkg/innerring/processors/netmap/nodevalidation/locode/util.go new file mode 100644 index 00000000..40d81601 --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/util.go @@ -0,0 +1,34 @@ +package locode + +type attrDescriptor struct { + optional bool + converter func(Record) string +} + +func countryCodeValue(r Record) (val string) { + return r.CountryCode().String() +} + +func countryValue(r Record) string { + return r.CountryName() +} + +func locationCodeValue(r Record) string { + return r.LocationCode().String() +} + +func locationValue(r Record) string { + return r.LocationName() +} + +func subDivCodeValue(r Record) string { + return r.SubDivCode() +} + +func subDivValue(r Record) string { + return r.SubDivName() +} + +func continentValue(r Record) string { + return r.Continent().String() +} diff --git a/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go b/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go new file mode 100644 index 00000000..e25878c9 --- /dev/null +++ b/pkg/innerring/processors/netmap/nodevalidation/locode/validator.go @@ -0,0 +1,67 @@ +package locode + +// Prm groups the required parameters of the Validator's constructor. +// +// All values must comply with the requirements imposed on them. +// Passing incorrect parameter values will result in constructor +// failure (error or panic depending on the implementation). +type Prm struct { + // NeoFS LOCODE database interface. + // + // Must not be nil. + DB DB +} + +// Validator is an utility that verifies and updates +// node attributes associated with its geographical location +// (LOCODE). +// +// For correct operation, Validator must be created +// using the constructor (New) based on the required parameters +// and optional components. After successful creation, +// the Validator is immediately ready to work through API. +type Validator struct { + db DB + + mAttr map[string]attrDescriptor +} + +// TODO: define constants in API lib. +const ( + attrKeyLocode = "Locode" + + attrKeyCountryCode = "CountryCode" + attrKeyCountry = "Country" + + attrKeyLocationCode = "CityCode" + attrKeyLocation = "City" + + attrKeySubDivCode = "SubDivCode" + attrKeySubDiv = "SubDiv" + + attrKeyContinent = "Continent" +) + +// New creates a new instance of the Validator. +// +// Panics if at least one value of the parameters is invalid. +// +// The created Validator does not require additional +// initialization and is completely ready for work. +func New(prm Prm) *Validator { + return &Validator{ + db: prm.DB, + mAttr: map[string]attrDescriptor{ + attrKeyCountryCode: {converter: countryCodeValue}, + attrKeyCountry: {converter: countryValue}, + + attrKeyLocationCode: {converter: locationCodeValue}, + attrKeyLocation: {converter: locationValue}, + + attrKeySubDivCode: {converter: subDivCodeValue, optional: true}, + attrKeySubDiv: {converter: subDivValue, optional: true}, + + attrKeyContinent: {converter: continentValue}, + }, + } +}