github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/generic_functions.go (about) 1 package govcd 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // oneOrError is used to cover up a common pattern in this codebase which is usually used in 9 // GetXByName functions. 10 // API endpoint returns N elements for an object we are looking (most commonly because API does not 11 // support filtering) and final filtering by Name must be done in code. 12 // After filtering returned entities one must be sure that exactly one was found and handle 3 cases: 13 // * If 0 entities are found - an error containing ErrorEntityNotFound must be returned 14 // * If >1 entities are found - an error containing the number of entities must be returned 15 // * If 1 entity was found - return it 16 // 17 // An example of code that was previously handled in non generic way - we had a lot of these 18 // occurrences throughout the code: 19 // 20 // if len(nsxtEdgeClusters) == 0 { 21 // // ErrorEntityNotFound is injected here for the ability to validate problem using ContainsNotFound() 22 // return nil, fmt.Errorf("%s: no NSX-T Tier-0 Edge Cluster with name '%s' for Org VDC with id '%s' found", 23 // ErrorEntityNotFound, name, vdc.Vdc.ID) 24 // } 25 26 // if len(nsxtEdgeClusters) > 1 { 27 // return nil, fmt.Errorf("more than one (%d) NSX-T Edge Cluster with name '%s' for Org VDC with id '%s' found", 28 // len(nsxtEdgeClusters), name, vdc.Vdc.ID) 29 // } 30 func oneOrError[E any](key, value string, entitySlice []*E) (*E, error) { 31 if len(entitySlice) > 1 { 32 return nil, fmt.Errorf("got more than one entity by %s '%s' %d", key, value, len(entitySlice)) 33 } 34 35 if len(entitySlice) == 0 { 36 // No entity found - returning ErrorEntityNotFound as it must be wrapped in the returned error 37 return nil, fmt.Errorf("%s: got zero entities by %s '%s'", ErrorEntityNotFound, key, value) 38 } 39 40 return entitySlice[0], nil 41 } 42 43 // localFilter performs filtering of a type E based on a field name `fieldName` and its 44 // expected string value `expectedFieldValue`. Common use case for GetAllX methods where API does 45 // not support filtering and it must be done on the client side. 46 // 47 // Note. The field name `fieldName` must be present in a given type E (letter casing is important) 48 func localFilter[E any](entityLabel string, entities []*E, fieldName, expectedFieldValue string) ([]*E, error) { 49 if len(entities) == 0 { 50 return nil, fmt.Errorf("zero entities provided for filtering") 51 } 52 53 filteredValues := make([]*E, 0) 54 for _, entity := range entities { 55 56 // Need to deference pointer because `reflect` package requires to work with types and not 57 // pointers to types 58 var entityValue E 59 if entity != nil { 60 entityValue = *entity 61 } else { 62 return nil, fmt.Errorf("given entity for %s is a nil pointer", entityLabel) 63 } 64 65 value := reflect.ValueOf(entityValue) 66 field := value.FieldByName(fieldName) 67 68 if !field.IsValid() { 69 return nil, fmt.Errorf("the struct for %s does not have the field '%s'", entityLabel, fieldName) 70 } 71 72 if field.Type().Name() != "string" { 73 return nil, fmt.Errorf("field '%s' is not string type, it has type '%s'", fieldName, field.Type().Name()) 74 } 75 76 if field.String() == expectedFieldValue { 77 filteredValues = append(filteredValues, entity) 78 } 79 } 80 81 return filteredValues, nil 82 } 83 84 // localFilterOneOrError performs local filtering using `genericLocalFilter()` and 85 // additionally verifies that only a single result is present using `oneOrError()`. Common use case 86 // for GetXByName methods where API does not support filtering and it must be done on client side. 87 func localFilterOneOrError[E any](entityLabel string, entities []*E, fieldName, expectedFieldValue string) (*E, error) { 88 if fieldName == "" || expectedFieldValue == "" { 89 return nil, fmt.Errorf("expected field name and value must be specified to filter %s", entityLabel) 90 } 91 92 filteredValues, err := localFilter(entityLabel, entities, fieldName, expectedFieldValue) 93 if err != nil { 94 return nil, err 95 } 96 97 return oneOrError(fieldName, expectedFieldValue, filteredValues) 98 }