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  }