github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/openapi_generic_inner_entities.go (about) 1 package govcd 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/url" 7 "strings" 8 ) 9 10 // crudConfig contains configuration that must be supplied when invoking generic functions that are defined 11 // in `openapi_generic_inner_entities.go` and `openapi_generic_outer_entities.go` 12 type crudConfig struct { 13 // Mandatory parameters 14 15 // entityLabel contains friendly entity name that is used for logging meaningful errors 16 entityLabel string 17 18 // endpoint in the usual format (e.g. types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointNsxtSegmentIpDiscoveryProfiles) 19 endpoint string 20 21 // Optional parameters 22 23 // endpointParams contains a slice of strings that will be used to construct the request URL. It will 24 // initially replace '%s' placeholders in the `endpoint` (if any) and will add them as suffix 25 // afterwards 26 endpointParams []string 27 28 // queryParameters will be passed as GET queries to the URL. Usually they are used for API filtering parameters 29 queryParameters url.Values 30 // additionalHeader can be used to pass additional headers for API calls. One of the common purposes is to pass 31 // tenant context 32 additionalHeader map[string]string 33 } 34 35 // validate should catch errors in consuming generic CRUD functions and should never produce false 36 // positives. 37 func (c crudConfig) validate() error { 38 // crudConfig misconfiguration - we can panic so that developer catches the problem during 39 // development of this SDK 40 if c.entityLabel == "" { 41 panic("'entityName' must always be specified when initializing crudConfig") 42 } 43 44 if c.endpoint == "" { 45 panic("'endpoint' must always be specified when initializing crudConfig") 46 } 47 48 // softer validations that consumers of this SDK can manipulate 49 50 // If `endpointParams` is specified in `crudConfig` (it is not nil), then it must contain at 51 // least a single non empty string parameter 52 // Such validation should prevent cases where some ID is not speficied upon function call. 53 // E.g.: endpointParams: []string{vdcId}, <--- vdcId comes from consumer of the SDK 54 // If the user specified empty `vdcId` - we'd validate this 55 for _, paramValue := range c.endpointParams { 56 if paramValue == "" { 57 return fmt.Errorf(`endpointParams were specified but they contain empty value "" for %s. %#v`, 58 c.entityLabel, c.endpointParams) 59 } 60 } 61 62 return nil 63 } 64 65 // createInnerEntity implements a common pattern for creating an entity throughout codebase 66 // Parameters: 67 // * `client` is a *Client 68 // * `c` holds settings for performing API call 69 // * `innerConfig` is the new entity type 70 func createInnerEntity[I any](client *Client, c crudConfig, innerConfig *I) (*I, error) { 71 if err := c.validate(); err != nil { 72 return nil, err 73 } 74 75 apiVersion, err := client.getOpenApiHighestElevatedVersion(c.endpoint) 76 if err != nil { 77 return nil, fmt.Errorf("error getting API version for creating entity '%s': %s", c.entityLabel, err) 78 } 79 80 exactEndpoint, err := urlFromEndpoint(c.endpoint, c.endpointParams) 81 if err != nil { 82 return nil, fmt.Errorf("error building endpoint '%s' with given params '%s' for entity '%s': %s", c.endpoint, strings.Join(c.endpointParams, ","), c.entityLabel, err) 83 } 84 85 urlRef, err := client.OpenApiBuildEndpoint(exactEndpoint) 86 if err != nil { 87 return nil, fmt.Errorf("error building API endpoint for entity '%s' creation: %s", c.entityLabel, err) 88 } 89 90 createdInnerEntityConfig := new(I) 91 err = client.OpenApiPostItem(apiVersion, urlRef, c.queryParameters, innerConfig, createdInnerEntityConfig, c.additionalHeader) 92 if err != nil { 93 return nil, fmt.Errorf("error creating entity of type '%s': %s", c.entityLabel, err) 94 } 95 96 return createdInnerEntityConfig, nil 97 } 98 99 // updateInnerEntity implements a common pattern for updating entity throughout codebase 100 // Parameters: 101 // * `client` is a *Client 102 // * `c` holds settings for performing API call 103 // * `innerConfig` is the new entity type 104 func updateInnerEntity[I any](client *Client, c crudConfig, innerConfig *I) (*I, error) { 105 // Discarding returned headers to better match return signature for most common cases 106 updatedInnerEntity, _, err := updateInnerEntityWithHeaders(client, c, innerConfig) 107 return updatedInnerEntity, err 108 } 109 110 // updateInnerEntityWithHeaders implements a common pattern for updating entity throughout codebase 111 // Parameters: 112 // * `client` is a *Client 113 // * `c` holds settings for performing API call 114 // * `innerConfig` is the new entity type 115 func updateInnerEntityWithHeaders[I any](client *Client, c crudConfig, innerConfig *I) (*I, http.Header, error) { 116 if err := c.validate(); err != nil { 117 return nil, nil, err 118 } 119 120 apiVersion, err := client.getOpenApiHighestElevatedVersion(c.endpoint) 121 if err != nil { 122 return nil, nil, fmt.Errorf("error getting API version for updating entity '%s': %s", c.entityLabel, err) 123 } 124 125 exactEndpoint, err := urlFromEndpoint(c.endpoint, c.endpointParams) 126 if err != nil { 127 return nil, nil, fmt.Errorf("error building endpoint '%s' with given params '%s' for entity '%s': %s", c.endpoint, strings.Join(c.endpointParams, ","), c.entityLabel, err) 128 } 129 130 urlRef, err := client.OpenApiBuildEndpoint(exactEndpoint) 131 if err != nil { 132 return nil, nil, fmt.Errorf("error building API endpoint for entity '%s' update: %s", c.entityLabel, err) 133 } 134 135 updatedInnerEntityConfig := new(I) 136 headers, err := client.OpenApiPutItemAndGetHeaders(apiVersion, urlRef, c.queryParameters, innerConfig, updatedInnerEntityConfig, c.additionalHeader) 137 if err != nil { 138 return nil, nil, fmt.Errorf("error updating entity of type '%s': %s", c.entityLabel, err) 139 } 140 141 return updatedInnerEntityConfig, headers, nil 142 } 143 144 // getInnerEntity is an implementation for a common pattern in our code where we have to retrieve 145 // outer entity (usually *types.XXXX) and does not need to be wrapped in an inner container entity. 146 // Parameters: 147 // * `client` is a *Client 148 // * `c` holds settings for performing API call 149 func getInnerEntity[I any](client *Client, c crudConfig) (*I, error) { 150 // Discarding returned headers to better match return signature for most common cases 151 innerEntity, _, err := getInnerEntityWithHeaders[I](client, c) 152 return innerEntity, err 153 } 154 155 // getInnerEntityWithHeaders is an implementation for a common pattern in our code where we have to retrieve 156 // outer entity (usually *types.XXXX) and does not need to be wrapped in an inner container entity. 157 // Parameters: 158 // * `client` is a *Client 159 // * `c` holds settings for performing API call 160 func getInnerEntityWithHeaders[I any](client *Client, c crudConfig) (*I, http.Header, error) { 161 if err := c.validate(); err != nil { 162 return nil, nil, err 163 } 164 165 apiVersion, err := client.getOpenApiHighestElevatedVersion(c.endpoint) 166 if err != nil { 167 return nil, nil, fmt.Errorf("error getting API version for entity '%s': %s", c.entityLabel, err) 168 } 169 170 exactEndpoint, err := urlFromEndpoint(c.endpoint, c.endpointParams) 171 if err != nil { 172 return nil, nil, fmt.Errorf("error building endpoint '%s' with given params '%s' for entity '%s': %s", c.endpoint, strings.Join(c.endpointParams, ","), c.entityLabel, err) 173 } 174 175 urlRef, err := client.OpenApiBuildEndpoint(exactEndpoint) 176 if err != nil { 177 return nil, nil, fmt.Errorf("error building API endpoint for entity '%s': %s", c.entityLabel, err) 178 } 179 180 typeResponse := new(I) 181 headers, err := client.OpenApiGetItemAndHeaders(apiVersion, urlRef, c.queryParameters, typeResponse, c.additionalHeader) 182 if err != nil { 183 return nil, nil, fmt.Errorf("error retrieving entity of type '%s': %s", c.entityLabel, err) 184 } 185 186 return typeResponse, headers, nil 187 } 188 189 // getAllInnerEntities can be used to retrieve a slice of any inner entities in the OpenAPI 190 // endpoints that are not nested in outer types 191 // 192 // Parameters: 193 // * `client` is a *Client 194 // * `c` holds settings for performing API call 195 func getAllInnerEntities[I any](client *Client, c crudConfig) ([]*I, error) { 196 if err := c.validate(); err != nil { 197 return nil, err 198 } 199 200 apiVersion, err := client.getOpenApiHighestElevatedVersion(c.endpoint) 201 if err != nil { 202 return nil, fmt.Errorf("error getting API version for entity '%s': %s", c.entityLabel, err) 203 } 204 205 exactEndpoint, err := urlFromEndpoint(c.endpoint, c.endpointParams) 206 if err != nil { 207 return nil, fmt.Errorf("error building endpoint '%s' with given params '%s' for entity '%s': %s", c.endpoint, strings.Join(c.endpointParams, ","), c.entityLabel, err) 208 } 209 210 urlRef, err := client.OpenApiBuildEndpoint(exactEndpoint) 211 if err != nil { 212 return nil, fmt.Errorf("error building API endpoint for entity '%s': %s", c.entityLabel, err) 213 } 214 215 typeResponses := make([]*I, 0) 216 err = client.OpenApiGetAllItems(apiVersion, urlRef, c.queryParameters, &typeResponses, c.additionalHeader) 217 if err != nil { 218 return nil, fmt.Errorf("error retrieving all entities of type '%s': %s", c.entityLabel, err) 219 } 220 221 return typeResponses, nil 222 } 223 224 // deleteEntityById performs a common operation for OpenAPI endpoints that calls DELETE method for a 225 // given endpoint. 226 // Note. It does not use generics for the operation, but is held in this file with other CRUD entries 227 // Parameters: 228 // * `client` is a *Client 229 // * `c` holds settings for performing API call 230 func deleteEntityById(client *Client, c crudConfig) error { 231 if err := c.validate(); err != nil { 232 return err 233 } 234 235 apiVersion, err := client.getOpenApiHighestElevatedVersion(c.endpoint) 236 if err != nil { 237 return err 238 } 239 240 exactEndpoint, err := urlFromEndpoint(c.endpoint, c.endpointParams) 241 if err != nil { 242 return fmt.Errorf("error building endpoint '%s' with given params '%s' for entity '%s': %s", c.endpoint, strings.Join(c.endpointParams, ","), c.entityLabel, err) 243 } 244 245 urlRef, err := client.OpenApiBuildEndpoint(exactEndpoint) 246 if err != nil { 247 return err 248 } 249 250 err = client.OpenApiDeleteItem(apiVersion, urlRef, c.queryParameters, c.additionalHeader) 251 252 if err != nil { 253 return fmt.Errorf("error deleting %s: %s", c.entityLabel, err) 254 } 255 256 return nil 257 } 258 259 func urlFromEndpoint(endpoint string, endpointParams []string) (string, error) { 260 // Count how many '%s' placeholders exist in the 'endpoint' 261 placeholderCount := strings.Count(endpoint, "%s") 262 263 // Validation. At the very minimum all placeholders must have their replacements - otherwise it 264 // is an error as we never want to query an endpoint that still has placeholders '%s' 265 if len(endpointParams) < placeholderCount { 266 return "", fmt.Errorf("endpoint '%s' has unpopulated placeholders", endpoint) 267 } 268 269 // if there are no 'endpointParams' - exit with the same endpoint 270 if len(endpointParams) == 0 { 271 return endpoint, nil 272 } 273 274 // Loop over given endpointParams and replace placeholders at first. Afterwards - amend any 275 // additional parameters to the end of endpoint 276 for _, v := range endpointParams { 277 // If there are placeholders '%s' to replace in the endpoint itself - do it 278 if placeholderCount > 0 { 279 endpoint = strings.Replace(endpoint, "%s", v, 1) 280 placeholderCount = placeholderCount - 1 281 continue 282 } 283 284 endpoint = endpoint + v 285 } 286 287 return endpoint, nil 288 }