github.com/cockroachdb/errors@v1.11.1/errbase/migrations.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 // implied. See the License for the specific language governing 13 // permissions and limitations under the License. 14 15 package errbase 16 17 import "fmt" 18 19 // This file provides the library with the ability to handle cases 20 // where an error type migrates, i.e. its package changes path or the 21 // type name is changed. 22 // 23 // There are several scenarios to contend with. Assuming the error 24 // type is initially called "foo", in version v1 of the code. 25 // 26 // Scenario 1: simple migration 27 // - v2 renames foo -> bar 28 // v2 calls: RegisterTypeMigration("foo", &bar{}) 29 // - v2 and v1 are connected 30 // - v1 sends an error to v2: 31 // - v2 has the migration registered, recognizes that "foo" 32 // refers to bar 33 // - v2 sends an error to v1 34 // - v2 rewrites the error key upon send to the name known to v1 35 // 36 // Scenario 2: simultaneous migration 37 // - vA renames foo -> bar 38 // vA calls RegisterTypeMigration("foo", &bar{}) 39 // - vB renames foo -> qux 40 // vB calls RegisterTypeMigration("foo", &qux{}) 41 // - vA and vB are connected 42 // - vA sends an error to vB: 43 // - vA translates the error key upon send from bar to foo's key 44 // - vB recognizes that "foo" refers to qux 45 // 46 // Scenario 3: migrated error passing through 47 // - v2 renames foo -> bar 48 // v2 calls: RegisterTypeMigration("foo", &bar{}) 49 // - v2.a, v2.b and v1 are connected: v2.a -> v1 -> v2.b 50 // - v2.a sends an error to v2.b via v1: 51 // - v2.a encodes using foo's key, v1 receives as foo 52 // - v1 encodes using foo's key 53 // - v2.b receive's foo's key, knows about migration, decodes as bar 54 // 55 // Scenario 4: migrated error passing through node that does not know 56 // about it whatsoever (the key is preserved). 57 // - v2 renames foo -> bar 58 // v2 calls: RegisterTypeMigration("foo", &bar{}) 59 // - v2.a, v2.b and v0 are connected: v2.a -> v0 -> v2.b 60 // (v0 does not know about error foo at all) 61 // - v2.a sends an error to v2.b via v0: 62 // - v2.a encodes using foo's key, v0 receives as "unknown foo" 63 // - v0 passes through unchanged 64 // - v2.b receive's foo's key, knows about migration, decodes as bar 65 // 66 // Scenario 5: comparison between migrated and non-migrated errors 67 // on 3rd party node. 68 // - v2 renames foo -> bar 69 // - v2 sends error bar to v0 70 // - v1 sends an equivalent error with type foo to v0 71 // - v0 (that doesn't know about the type) compares the two errors. 72 // Here we're expecting v0 to properly ascertain the errors are equivalent. 73 74 // RegisterTypeMigration tells the library that the type of the error 75 // given as 3rd argument was previously known with type 76 // previousTypeName, located at previousPkgPath. 77 // 78 // The value of previousTypeName must be the result of calling 79 // reflect.TypeOf(err).String() on the original error object. 80 // This is usually composed as follows: 81 // [*]<shortpackage>.<errortype> 82 // 83 // For example, Go's standard error type has name "*errors.errorString". 84 // The asterisk indicates that `errorString` implements the `error` 85 // interface via pointer receiver. 86 // 87 // Meanwhile, the singleton error type context.DeadlineExceeded 88 // has name "context.deadlineExceededError", without asterisk 89 // because the type implements `error` by value. 90 // 91 // Remember that the short package name inside the error type name and 92 // the last component of the package path can be different. This is 93 // why they must be specified separately. 94 func RegisterTypeMigration(previousPkgPath, previousTypeName string, newType error) { 95 prevKey := TypeKey(makeTypeKey(previousPkgPath, previousTypeName)) 96 newKey := TypeKey(getFullTypeName(newType)) 97 98 // Register the backward migration: make the encode function 99 // aware of the old name. 100 if f, ok := backwardRegistry[newKey]; ok { 101 panic(fmt.Errorf("migration to type %q already registered (from %q)", newKey, f)) 102 } 103 backwardRegistry[newKey] = prevKey 104 // If any other key was registered as a migration from newKey, 105 // we'll forward those as well. 106 // This changes X -> newKey to X -> prevKey for every X. 107 for new, prev := range backwardRegistry { 108 if prev == newKey { 109 backwardRegistry[new] = prevKey 110 } 111 } 112 } 113 114 // registry used when encoding an error, so that the receiver observes 115 // the original key. This maps new keys to old keys. 116 var backwardRegistry = map[TypeKey]TypeKey{} 117 118 // TestingWithEmptyMigrationRegistry is intended for use by tests. 119 func TestingWithEmptyMigrationRegistry() (restore func()) { 120 save := backwardRegistry 121 backwardRegistry = map[TypeKey]TypeKey{} 122 return func() { backwardRegistry = save } 123 }