// Copyright 2019 The Cockroach Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. See the License for the specific language governing // permissions and limitations under the License. package errbase import "fmt" // This file provides the library with the ability to handle cases // where an error type migrates, i.e. its package changes path or the // type name is changed. // // There are several scenarios to contend with. Assuming the error // type is initially called "foo", in version v1 of the code. // // Scenario 1: simple migration // - v2 renames foo -> bar // v2 calls: RegisterTypeMigration("foo", &bar{}) // - v2 and v1 are connected // - v1 sends an error to v2: // - v2 has the migration registered, recognizes that "foo" // refers to bar // - v2 sends an error to v1 // - v2 rewrites the error key upon send to the name known to v1 // // Scenario 2: simultaneous migration // - vA renames foo -> bar // vA calls RegisterTypeMigration("foo", &bar{}) // - vB renames foo -> qux // vB calls RegisterTypeMigration("foo", &qux{}) // - vA and vB are connected // - vA sends an error to vB: // - vA translates the error key upon send from bar to foo's key // - vB recognizes that "foo" refers to qux // // Scenario 3: migrated error passing through // - v2 renames foo -> bar // v2 calls: RegisterTypeMigration("foo", &bar{}) // - v2.a, v2.b and v1 are connected: v2.a -> v1 -> v2.b // - v2.a sends an error to v2.b via v1: // - v2.a encodes using foo's key, v1 receives as foo // - v1 encodes using foo's key // - v2.b receive's foo's key, knows about migration, decodes as bar // // Scenario 4: migrated error passing through node that does not know // about it whatsoever (the key is preserved). // - v2 renames foo -> bar // v2 calls: RegisterTypeMigration("foo", &bar{}) // - v2.a, v2.b and v0 are connected: v2.a -> v0 -> v2.b // (v0 does not know about error foo at all) // - v2.a sends an error to v2.b via v0: // - v2.a encodes using foo's key, v0 receives as "unknown foo" // - v0 passes through unchanged // - v2.b receive's foo's key, knows about migration, decodes as bar // // Scenario 5: comparison between migrated and non-migrated errors // on 3rd party node. // - v2 renames foo -> bar // - v2 sends error bar to v0 // - v1 sends an equivalent error with type foo to v0 // - v0 (that doesn't know about the type) compares the two errors. // Here we're expecting v0 to properly ascertain the errors are equivalent. // RegisterTypeMigration tells the library that the type of the error // given as 3rd argument was previously known with type // previousTypeName, located at previousPkgPath. // // The value of previousTypeName must be the result of calling // reflect.TypeOf(err).String() on the original error object. // This is usually composed as follows: // [*]. // // For example, Go's standard error type has name "*errors.errorString". // The asterisk indicates that `errorString` implements the `error` // interface via pointer receiver. // // Meanwhile, the singleton error type context.DeadlineExceeded // has name "context.deadlineExceededError", without asterisk // because the type implements `error` by value. // // Remember that the short package name inside the error type name and // the last component of the package path can be different. This is // why they must be specified separately. func RegisterTypeMigration(previousPkgPath, previousTypeName string, newType error) { prevKey := TypeKey(makeTypeKey(previousPkgPath, previousTypeName)) newKey := TypeKey(getFullTypeName(newType)) // Register the backward migration: make the encode function // aware of the old name. if f, ok := backwardRegistry[newKey]; ok { panic(fmt.Errorf("migration to type %q already registered (from %q)", newKey, f)) } backwardRegistry[newKey] = prevKey // If any other key was registered as a migration from newKey, // we'll forward those as well. // This changes X -> newKey to X -> prevKey for every X. for new, prev := range backwardRegistry { if prev == newKey { backwardRegistry[new] = prevKey } } } // registry used when encoding an error, so that the receiver observes // the original key. This maps new keys to old keys. var backwardRegistry = map[TypeKey]TypeKey{} // TestingWithEmptyMigrationRegistry is intended for use by tests. func TestingWithEmptyMigrationRegistry() (restore func()) { save := backwardRegistry backwardRegistry = map[TypeKey]TypeKey{} return func() { backwardRegistry = save } }