diff --git a/README.md b/README.md index 5d16f1217..be470a817 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,57 @@ $ make testunit If you want to go off of an example, we recommend using the [external contacts](https://github.com/MyPureCloud/terraform-provider-genesyscloud/tree/main/genesyscloud/external_contacts) package. +### Custom API Client + +When a proxy function needs to call a Genesys Cloud API endpoint that doesn't have a generated SDK method, use the `custom_api_client` package (`genesyscloud/custom_api_client`) instead of calling `APIClient.CallAPI()` directly or constructing raw `http.Client` requests. This package handles authorization headers, content-type negotiation, query parameter encoding, error handling, and SDK debug logging context automatically. + +```go +import customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" +``` + +**Available functions:** + +| Function | Use case | +|----------|----------| +| `Do[T]` | Typed JSON response — unmarshals into `T` | +| `DoNoResponse` | No response body (DELETE, PUT with no return) | +| `DoRaw` | Raw `[]byte` response for custom unmarshaling | +| `DoWithAcceptHeader` | Non-JSON responses (e.g., `text/csv`) | + +**Example — adding to a proxy struct:** + +```go +type myProxy struct { + clientConfig *platformclientv2.Configuration + myApi *platformclientv2.MyApi + customApiClient *customapi.Client +} + +func newMyProxy(clientConfig *platformclientv2.Configuration) *myProxy { + return &myProxy{ + clientConfig: clientConfig, + myApi: platformclientv2.NewMyApiWithConfig(clientConfig), + customApiClient: customapi.NewClient(clientConfig, ResourceType), + } +} +``` + +**Example — making a call:** + +```go +// Typed response +result, resp, err := customapi.Do[platformclientv2.MyEntity](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/my/endpoint", nil, nil) + +// With query params +qp := customapi.NewQueryParams(map[string]string{"pageSize": "100", "pageNumber": "1"}) +result, resp, err := customapi.Do[platformclientv2.MyListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/my/endpoint", nil, qp) + +// Delete with no response +resp, err := customapi.DoNoResponse(ctx, p.customApiClient, customapi.MethodDelete, "/api/v2/my/endpoint/"+id, nil, nil) +``` + +See the [package documentation](genesyscloud/custom_api_client/custom_api_client.go) for full details and additional examples. + ### Documentation Generation The provider documentation is automatically generated from resource schemas and example files. Understanding this process is crucial when adding new resources. diff --git a/genesyscloud/architect_datatable/resource_genesyscloud_architect_datatable_proxy.go b/genesyscloud/architect_datatable/resource_genesyscloud_architect_datatable_proxy.go index 9846c8b4e..35e76d730 100644 --- a/genesyscloud/architect_datatable/resource_genesyscloud_architect_datatable_proxy.go +++ b/genesyscloud/architect_datatable/resource_genesyscloud_architect_datatable_proxy.go @@ -2,10 +2,8 @@ package architect_datatable import ( "context" - "encoding/json" - "errors" - "net/http" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" @@ -23,6 +21,7 @@ type getAllArchitectDatatableFunc func(ctx context.Context, p *architectDatatabl type architectDatatableProxy struct { clientConfig *platformclientv2.Configuration architectApi *platformclientv2.ArchitectApi + customApiClient *customapi.Client createOrUpdateArchitectDatatableAttr createOrUpdateArchitectDatatableFunc getArchitectDatatableAttr getArchitectDatatableFunc getAllArchitectDatatableAttr getAllArchitectDatatableFunc @@ -34,6 +33,7 @@ func newArchitectDatatableProxy(clientConfig *platformclientv2.Configuration) *a return &architectDatatableProxy{ clientConfig: clientConfig, architectApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), createOrUpdateArchitectDatatableAttr: createOrUpdateArchitectDatatableFn, getArchitectDatatableAttr: getArchitectDatatableFn, getAllArchitectDatatableAttr: getAllArchitectDatatableFn, @@ -73,78 +73,24 @@ func createOrUpdateArchitectDatatableFn(ctx context.Context, p *architectDatatab // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - apiClient := &p.architectApi.Configuration.APIClient - action := http.MethodPost - - // create path and map variables - path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables" + method := customapi.MethodPost + path := "/api/v2/flows/datatables" if !createAction { - action = http.MethodPut + method = customapi.MethodPut path += "/" + *datatable.Id } - headerParams := make(map[string]string) - - // add default headers if any - for key := range p.architectApi.Configuration.DefaultHeader { - headerParams[key] = p.architectApi.Configuration.DefaultHeader[key] - } - - headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *Datatable - response, err := apiClient.CallAPI(path, action, datatable, headerParams, nil, nil, "", nil, "") - - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - - return successPayload, response, err + return customapi.Do[Datatable](ctx, p.customApiClient, method, path, datatable, nil) } func getArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string, expanded string) (*Datatable, *platformclientv2.APIResponse, error) { // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - apiClient := &p.architectApi.Configuration.APIClient + queryParams := customapi.NewQueryParams(map[string]string{"expand": expanded}) - // create path and map variables - path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if p.architectApi.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken - } - // add default headers if any - for key := range p.architectApi.Configuration.DefaultHeader { - headerParams[key] = p.architectApi.Configuration.DefaultHeader[key] - } - - queryParams["expand"] = apiClient.ParameterToString(expanded, "") - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *Datatable - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal(response.RawBody, &successPayload) - } - return successPayload, response, err + return customapi.Do[Datatable](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows/datatables/"+datatableId, nil, queryParams) } func deleteArchitectDatatableFn(ctx context.Context, p *architectDatatableProxy, datatableId string) (*platformclientv2.APIResponse, error) { diff --git a/genesyscloud/architect_datatable_row/resource_genesyscloud_architect_datatable_row_proxy.go b/genesyscloud/architect_datatable_row/resource_genesyscloud_architect_datatable_row_proxy.go index 65ac3c046..0cddc3ccb 100644 --- a/genesyscloud/architect_datatable_row/resource_genesyscloud_architect_datatable_row_proxy.go +++ b/genesyscloud/architect_datatable_row/resource_genesyscloud_architect_datatable_row_proxy.go @@ -2,12 +2,10 @@ package architect_datatable_row import ( "context" - "encoding/json" - "errors" "log" - "net/http" "github.com/mitchellh/mapstructure" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" @@ -26,6 +24,7 @@ type deleteArchitectDatatableRowFunc func(ctx context.Context, p *architectDatat type architectDatatableRowProxy struct { clientConfig *platformclientv2.Configuration architectApi *platformclientv2.ArchitectApi + customApiClient *customapi.Client createArchitectDatatableRowAttr createArchitectDatatableRowFunc getArchitectDatatableAttr getArchitectDatatableFunc getAllArchitectDatatableAttr getAllArchitectDatatableFunc @@ -47,6 +46,7 @@ func newArchitectDatatableRowProxy(clientConfig *platformclientv2.Configuration) return &architectDatatableRowProxy{ clientConfig: clientConfig, architectApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), dataTableRowCache: dataTableRowCache, dataTableCache: dataTableCache, getArchitectDatatableAttr: getArchitectDatatableFn, @@ -149,38 +149,9 @@ func getArchitectDatatableFn(ctx context.Context, p *architectDatatableRowProxy, return eg, nil, nil } - apiClient := &p.architectApi.Configuration.APIClient + queryParams := customapi.NewQueryParams(map[string]string{"expand": expanded}) - // create path and map variables - path := p.architectApi.Configuration.BasePath + "/api/v2/flows/datatables/" + datatableId - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if p.architectApi.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + p.architectApi.Configuration.AccessToken - } - // add default headers if any - for key := range p.architectApi.Configuration.DefaultHeader { - headerParams[key] = p.architectApi.Configuration.DefaultHeader[key] - } - - queryParams["expand"] = apiClient.ParameterToString(expanded, "") - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *Datatable - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal(response.RawBody, &successPayload) - } - return successPayload, response, err + return customapi.Do[Datatable](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows/datatables/"+datatableId, nil, queryParams) } func getAllArchitectDatatableRowsFn(ctx context.Context, p *architectDatatableRowProxy, tableId string) (*[]map[string]interface{}, *platformclientv2.APIResponse, error) { diff --git a/genesyscloud/architect_flow/resource_genesyscloud_architect_flow_proxy.go b/genesyscloud/architect_flow/resource_genesyscloud_architect_flow_proxy.go index a9b465bbf..e7b8642bc 100644 --- a/genesyscloud/architect_flow/resource_genesyscloud_architect_flow_proxy.go +++ b/genesyscloud/architect_flow/resource_genesyscloud_architect_flow_proxy.go @@ -2,14 +2,12 @@ package architect_flow import ( "context" - "encoding/json" "fmt" - "io" "log" "net/http" - "net/url" "time" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" @@ -33,8 +31,9 @@ type getExportJobStatusByIdFunc func(a *architectFlowProxy, jobId string) (*plat type pollExportJobForDownloadUrlFunc func(a *architectFlowProxy, jobId string, timeoutInSeconds float64) (downloadUrl string, err error) type architectFlowProxy struct { - clientConfig *platformclientv2.Configuration - api *platformclientv2.ArchitectApi + clientConfig *platformclientv2.Configuration + customApiClient *customapi.Client + api *platformclientv2.ArchitectApi getArchitectFlowAttr getArchitectFunc getAllArchitectFlowsAttr getAllArchitectFlowsFunc @@ -56,8 +55,9 @@ var flowCache = rc.NewResourceCache[platformclientv2.Flow]() func newArchitectFlowProxy(clientConfig *platformclientv2.Configuration) *architectFlowProxy { api := platformclientv2.NewArchitectApiWithConfig(clientConfig) return &architectFlowProxy{ - clientConfig: clientConfig, - api: api, + clientConfig: clientConfig, + customApiClient: customapi.NewClient(clientConfig, ResourceType), + api: api, getArchitectFlowAttr: getArchitectFlowFn, getAllArchitectFlowsAttr: getAllArchitectFlowsFn, @@ -322,49 +322,35 @@ func getArchitectFlowJobsFn(ctx context.Context, p *architectFlowProxy, jobId st // getAllArchitectFlowsFn is the implementation function for GetAllFlows func getAllArchitectFlowsFn(ctx context.Context, p *architectFlowProxy, name string, varType []string) (*[]platformclientv2.Flow, *platformclientv2.APIResponse, error) { - baseURL := p.clientConfig.BasePath + "/api/v2/flows" ctx = provider.EnsureResourceContext(ctx, ResourceType) + var allFlows []platformclientv2.Flow - params := url.Values{} + queryParams := customapi.QueryParams{} + queryParams.Set("pageSize", "100") + queryParams.Set("pageNumber", "1") + queryParams.Set("includeSchemas", "true") if name != "" { - params.Add("name", name) + queryParams.Set("name", name) } for _, t := range varType { - params.Add("type", t) - } - params.Add("includeSchemas", "true") - - client := &http.Client{} - var allFlows []platformclientv2.Flow - - params.Set("pageSize", "100") - params.Set("pageNumber", "1") - - u, err := url.Parse(baseURL) - if err != nil { - return nil, nil, fmt.Errorf("error parsing URL: %v", err) + queryParams.Add("type", t) } - u.RawQuery = params.Encode() - flows, apiResp, err := makeFlowRequest(ctx, client, u.String(), p) + flows, apiResp, err := customapi.Do[platformclientv2.Flowentitylisting](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows", nil, queryParams) if err != nil { return nil, apiResp, err } - if flows.Entities != nil { allFlows = append(allFlows, *flows.Entities...) } for pageNum := 2; pageNum <= *flows.PageCount; pageNum++ { ctx = provider.EnsureResourceContext(ctx, ResourceType) - params.Set("pageNumber", fmt.Sprintf("%d", pageNum)) - u.RawQuery = params.Encode() - - pageFlows, _, err := makeFlowRequest(ctx, client, u.String(), p) + queryParams.Set("pageNumber", fmt.Sprintf("%d", pageNum)) + pageFlows, _, err := customapi.Do[platformclientv2.Flowentitylisting](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/flows", nil, queryParams) if err != nil { return nil, apiResp, err } - if pageFlows.Entities != nil { allFlows = append(allFlows, *pageFlows.Entities...) } @@ -373,50 +359,9 @@ func getAllArchitectFlowsFn(ctx context.Context, p *architectFlowProxy, name str for _, flow := range allFlows { rc.SetCache(p.flowCache, *flow.Id, flow) } - return &allFlows, apiResp, nil } -func makeFlowRequest(ctx context.Context, client *http.Client, url string, p *architectFlowProxy) (*platformclientv2.Flowentitylisting, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request - ctx = provider.EnsureResourceContext(ctx, ResourceType) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, nil, err - } - - req.Header.Set("Authorization", "Bearer "+p.clientConfig.AccessToken) - req.Header.Set("Content-Type", "application/json") - - resp, err := client.Do(req) - if err != nil { - return nil, nil, fmt.Errorf("error making request: %v", err) - } - defer resp.Body.Close() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, nil, fmt.Errorf("error reading response: %v", err) - } - - apiResp := &platformclientv2.APIResponse{ - StatusCode: resp.StatusCode, - Response: resp, - } - - if resp.StatusCode >= 400 { - return nil, apiResp, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody)) - } - - var flows platformclientv2.Flowentitylisting - if err := json.Unmarshal(respBody, &flows); err != nil { - return nil, apiResp, err - } - - return &flows, apiResp, nil -} - // generateDownloadUrlFn is the implementation function for the generateDownloadUrl method func generateDownloadUrlFn(a *architectFlowProxy, flowId string) (downloadUrl string, err error) { defer func() { diff --git a/genesyscloud/business_rules_schema/genesyscloud_business_rules_schema_proxy.go b/genesyscloud/business_rules_schema/genesyscloud_business_rules_schema_proxy.go index 0b62504e3..ad520d341 100644 --- a/genesyscloud/business_rules_schema/genesyscloud_business_rules_schema_proxy.go +++ b/genesyscloud/business_rules_schema/genesyscloud_business_rules_schema_proxy.go @@ -3,11 +3,10 @@ package business_rules_schema import ( "context" "encoding/json" - "errors" "fmt" "log" - "net/http" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" @@ -37,6 +36,7 @@ type getBusinessRulesSchemaDeletedStatusFunc func(ctx context.Context, p *busine type businessRulesSchemaProxy struct { clientConfig *platformclientv2.Configuration businessRulesApi *platformclientv2.BusinessRulesApi + customApiClient *customapi.Client createBusinessRulesSchemaAttr createBusinessRulesSchemaFunc getAllBusinessRulesSchemaAttr getAllBusinessRulesSchemaFunc getBusinessRulesSchemasByNameAttr getBusinessRulesSchemasByNameFunc @@ -55,6 +55,7 @@ func newBusinessRulesSchemaProxy(clientConfig *platformclientv2.Configuration) * return &businessRulesSchemaProxy{ clientConfig: clientConfig, businessRulesApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), createBusinessRulesSchemaAttr: createBusinessRulesSchemaFn, getAllBusinessRulesSchemaAttr: getAllBusinessRulesSchemaFn, getBusinessRulesSchemasByNameAttr: getBusinessRulesSchemasByNameFn, @@ -208,47 +209,17 @@ func deleteBusinessRulesSchemaFn(ctx context.Context, p *businessRulesSchemaProx // getBusinessRulesSchemaDeletedStatusFn is an implementation function to get the 'deleted' status of a Genesys Cloud business rules schema func getBusinessRulesSchemaDeletedStatusFn(ctx context.Context, p *businessRulesSchemaProxy, schemaId string) (isDeleted bool, resp *platformclientv2.APIResponse, err error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - apiClient := &p.clientConfig.APIClient - // create path and map variables - path := p.clientConfig.BasePath + "/api/v2/businessrules/schemas/" + schemaId - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if p.clientConfig.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + p.clientConfig.AccessToken - } - // add default headers if any - for key := range p.clientConfig.DefaultHeader { - headerParams[key] = p.clientConfig.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload map[string]interface{} - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") + rawBody, resp, err := customapi.DoRaw(ctx, p.customApiClient, customapi.MethodGet, "/api/v2/businessrules/schemas/"+schemaId, nil, nil) if err != nil { - return false, response, fmt.Errorf("failed to get business rules schema %s: %v", schemaId, err) - } - if response.Error != nil { - return false, response, fmt.Errorf("failed to get business rules schema %s: %v", schemaId, errors.New(response.ErrorMessage)) + return false, resp, fmt.Errorf("failed to get business rules schema %s: %v", schemaId, err) } - - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - if err != nil { - return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) + var result map[string]interface{} + if err := json.Unmarshal(rawBody, &result); err != nil { + return false, resp, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) } - - // Manually query for the 'deleted' property because it is removed when - // response JSON body becomes SDK Dataschema object. - if isDeleted, ok := successPayload["deleted"].(bool); ok { - return isDeleted, response, nil + if deleted, ok := result["deleted"].(bool); ok { + return deleted, resp, nil } - - return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) + return false, resp, fmt.Errorf("failed to get deleted status of %s", schemaId) } diff --git a/genesyscloud/custom_api_client/custom_api_client.go b/genesyscloud/custom_api_client/custom_api_client.go new file mode 100644 index 000000000..63cd870af --- /dev/null +++ b/genesyscloud/custom_api_client/custom_api_client.go @@ -0,0 +1,234 @@ +// Package custom_api_client provides a lightweight wrapper around the Platform SDK's +// APIClient.CallAPI() method, eliminating the boilerplate required when calling Genesys +// Cloud API endpoints that don't have generated SDK methods. +// +// # When to use this package +// +// Use custom_api_client when a proxy function needs to call a Platform API endpoint +// that is not available as a typed method on the SDK's API classes (e.g., RoutingApi, +// UsersApi). This typically happens when: +// - The SDK hasn't been updated to include a new endpoint yet +// - The endpoint requires query parameter combinations the SDK doesn't support +// - You need raw byte access to the response body +// +// Do NOT use this package for: +// - Endpoints that already have SDK methods — use the SDK directly +// - S3 uploads or presigned URL flows — use net/http directly +// +// # Available functions +// +// - Do[T] — Generic typed response (JSON unmarshaled into T) +// - DoNoResponse — No response body expected (DELETE, PUT with no return) +// - DoRaw — Returns raw []byte response for custom unmarshaling +// - DoWithAcceptHeader — Custom Accept header (e.g., "text/csv") +// +// # Usage in a proxy struct +// +// Add a customApiClient field to the proxy struct and initialize it in the constructor: +// +// import customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" +// +// type myProxy struct { +// clientConfig *platformclientv2.Configuration +// myApi *platformclientv2.MyApi +// customApiClient *customapi.Client +// } +// +// func newMyProxy(clientConfig *platformclientv2.Configuration) *myProxy { +// return &myProxy{ +// clientConfig: clientConfig, +// myApi: platformclientv2.NewMyApiWithConfig(clientConfig), +// customApiClient: customapi.NewClient(clientConfig, ResourceType), +// } +// } +// +// Then call from a proxy function: +// +// result, resp, err := customapi.Do[platformclientv2.MyEntity](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/my/endpoint", nil, nil) +// +// # Usage in a standalone function (no proxy struct) +// +// c := customapi.NewClient(api.Configuration, ResourceType) +// _, err := customapi.DoNoResponse(ctx, c, customapi.MethodDelete, "/api/v2/things/"+id, nil, nil) +// +// # Query parameters +// +// Use NewQueryParams for simple key-value pairs, or QueryParams directly for multi-value keys: +// +// // Simple +// qp := customapi.NewQueryParams(map[string]string{"pageSize": "100"}) +// +// // Multi-value (e.g., ?type=inboundcall&type=outboundcall) +// qp := customapi.QueryParams{} +// qp.Add("type", "inboundcall") +// qp.Add("type", "outboundcall") +// +// # Testing +// +// The Client uses a function attribute (callAPIAttr) for the underlying SDK call, +// following the same pattern as proxy files. Unit tests within this package inject +// a mock callAPIAttr directly. See custom_api_client_test.go for examples. +package custom_api_client + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/url" + + "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" + "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" +) + +// callAPIFunc is the function signature matching the SDK's APIClient.CallAPI method. +type callAPIFunc func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) + +// Client wraps the SDK's APIClient.CallAPI() to eliminate boilerplate +// for custom platform API calls not covered by generated SDK methods. +type Client struct { + config *platformclientv2.Configuration + resourceType string + callAPIAttr callAPIFunc +} + +// NewClient creates a new custom API client. +func NewClient(config *platformclientv2.Configuration, resourceType string) *Client { + return &Client{ + config: config, + resourceType: resourceType, + callAPIAttr: config.APIClient.CallAPI, + } +} + +// Config returns the underlying SDK configuration. +func (c *Client) Config() *platformclientv2.Configuration { + return c.config +} + +// buildHeaders constructs the standard header map from the SDK configuration. +func (c *Client) buildHeaders() map[string]string { + headers := make(map[string]string) + for key := range c.config.DefaultHeader { + headers[key] = c.config.DefaultHeader[key] + } + if c.config.AccessToken != "" { + headers["Authorization"] = "Bearer " + c.config.AccessToken + } + headers["Content-Type"] = "application/json" + headers["Accept"] = "application/json" + return headers +} + +// buildPath constructs the full URL path with query parameters encoded. +// We encode query params into the path ourselves and pass nil to CallAPI's queryParams +// so that url.Values (which supports multi-value keys) works correctly. +func (c *Client) buildPath(path string, queryParams url.Values) string { + fullPath := c.config.BasePath + path + if len(queryParams) > 0 { + fullPath += "?" + queryParams.Encode() + } + return fullPath +} + +// Do makes a platform API call and unmarshals the response into T. +// path is relative, e.g. "/api/v2/processAutomation/triggers". +// body is passed directly to CallAPI as postBody (can be nil for GET/DELETE). +// queryParams can be nil. Supports multi-value keys via url.Values. +func Do[T any](ctx context.Context, c *Client, method, path string, body interface{}, queryParams url.Values) (*T, *platformclientv2.APIResponse, error) { + ctx = provider.EnsureResourceContext(ctx, c.resourceType) + + fullPath := c.buildPath(path, queryParams) + headers := c.buildHeaders() + + response, err := c.callAPIAttr(fullPath, method, body, headers, nil, nil, "", nil, "") + if err != nil { + return nil, response, err + } + if response.Error != nil { + return nil, response, errors.New(response.ErrorMessage) + } + + var result T + if err := json.Unmarshal(response.RawBody, &result); err != nil { + return nil, response, err + } + return &result, response, nil +} + +// DoNoResponse makes a platform API call that returns no body (e.g. DELETE). +func DoNoResponse(ctx context.Context, c *Client, method, path string, body interface{}, queryParams url.Values) (*platformclientv2.APIResponse, error) { + ctx = provider.EnsureResourceContext(ctx, c.resourceType) + + fullPath := c.buildPath(path, queryParams) + headers := c.buildHeaders() + + response, err := c.callAPIAttr(fullPath, method, body, headers, nil, nil, "", nil, "") + if err != nil { + return response, err + } + if response.Error != nil { + return response, errors.New(response.ErrorMessage) + } + return response, nil +} + +// DoRaw makes a platform API call and returns the raw response body as bytes. +// Useful when the caller needs to inspect the raw JSON (e.g. checking for a "deleted" field +// that gets stripped by SDK type unmarshaling). +func DoRaw(ctx context.Context, c *Client, method, path string, body interface{}, queryParams url.Values) ([]byte, *platformclientv2.APIResponse, error) { + ctx = provider.EnsureResourceContext(ctx, c.resourceType) + + fullPath := c.buildPath(path, queryParams) + headers := c.buildHeaders() + + response, err := c.callAPIAttr(fullPath, method, body, headers, nil, nil, "", nil, "") + if err != nil { + return nil, response, err + } + if response.Error != nil { + return nil, response, errors.New(response.ErrorMessage) + } + return response.RawBody, response, nil +} + +// DoWithAcceptHeader makes a platform API call with a custom Accept header. +// Useful for endpoints that return non-JSON content (e.g. templates returning text/*). +func DoWithAcceptHeader(ctx context.Context, c *Client, method, path string, body interface{}, queryParams url.Values, accept string) ([]byte, *platformclientv2.APIResponse, error) { + ctx = provider.EnsureResourceContext(ctx, c.resourceType) + + fullPath := c.buildPath(path, queryParams) + headers := c.buildHeaders() + headers["Accept"] = accept + + response, err := c.callAPIAttr(fullPath, method, body, headers, nil, nil, "", nil, "") + if err != nil { + return nil, response, err + } + if response.Error != nil { + return nil, response, errors.New(response.ErrorMessage) + } + return response.RawBody, response, nil +} + +// MethodGet and friends are convenience constants so callers don't need to import net/http. +const ( + MethodGet = http.MethodGet + MethodPost = http.MethodPost + MethodPut = http.MethodPut + MethodPatch = http.MethodPatch + MethodDelete = http.MethodDelete +) + +// QueryParams is an alias for url.Values so callers don't need to import net/url. +type QueryParams = url.Values + +// NewQueryParams builds a QueryParams from simple key-value pairs. +// For multi-value keys, use the returned QueryParams.Add() method. +func NewQueryParams(params map[string]string) QueryParams { + qp := make(url.Values, len(params)) + for k, v := range params { + qp.Set(k, v) + } + return qp +} diff --git a/genesyscloud/custom_api_client/custom_api_client_test.go b/genesyscloud/custom_api_client/custom_api_client_test.go new file mode 100644 index 000000000..236628dea --- /dev/null +++ b/genesyscloud/custom_api_client/custom_api_client_test.go @@ -0,0 +1,342 @@ +package custom_api_client + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "testing" + + "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" + "github.com/stretchr/testify/assert" +) + +// helper to build a Client with a mocked callAPIAttr +func newTestClient(mockFn callAPIFunc) *Client { + config := &platformclientv2.Configuration{ + BasePath: "https://api.example.com", + AccessToken: "test-token", + DefaultHeader: map[string]string{"X-Custom": "custom-value"}, + } + return &Client{ + config: config, + resourceType: "genesyscloud_test_resource", + callAPIAttr: mockFn, + } +} + +func TestNewClient(t *testing.T) { + config := platformclientv2.GetDefaultConfiguration() + config.BasePath = "https://api.example.com" + config.AccessToken = "test-token" + + client := NewClient(config, "genesyscloud_test_resource") + + assert.NotNil(t, client) + assert.Equal(t, config, client.Config()) + assert.Equal(t, "genesyscloud_test_resource", client.resourceType) + assert.NotNil(t, client.callAPIAttr) +} + +func TestBuildHeaders(t *testing.T) { + config := &platformclientv2.Configuration{ + AccessToken: "my-token", + DefaultHeader: map[string]string{"X-Custom": "value"}, + } + client := &Client{config: config} + + headers := client.buildHeaders() + + assert.Equal(t, "Bearer my-token", headers["Authorization"]) + assert.Equal(t, "application/json", headers["Content-Type"]) + assert.Equal(t, "application/json", headers["Accept"]) + assert.Equal(t, "value", headers["X-Custom"]) +} + +func TestBuildHeadersNoToken(t *testing.T) { + config := &platformclientv2.Configuration{ + DefaultHeader: map[string]string{}, + } + client := &Client{config: config} + + headers := client.buildHeaders() + + _, hasAuth := headers["Authorization"] + assert.False(t, hasAuth) +} + +func TestBuildPath(t *testing.T) { + config := &platformclientv2.Configuration{BasePath: "https://api.example.com"} + client := &Client{config: config} + + // No query params + assert.Equal(t, "https://api.example.com/api/v2/things", client.buildPath("/api/v2/things", nil)) + + // Single value params + params := url.Values{"pageSize": {"100"}, "pageNumber": {"1"}} + path := client.buildPath("/api/v2/things", params) + assert.Contains(t, path, "https://api.example.com/api/v2/things?") + assert.Contains(t, path, "pageSize=100") + assert.Contains(t, path, "pageNumber=1") + + // Multi-value params + multiParams := url.Values{"type": {"inboundcall", "outboundcall"}} + multiPath := client.buildPath("/api/v2/flows", multiParams) + assert.Contains(t, multiPath, "type=inboundcall") + assert.Contains(t, multiPath, "type=outboundcall") +} + +func TestDoSuccess(t *testing.T) { + type TestResponse struct { + Id string `json:"id"` + Name string `json:"name"` + } + + expectedBody, _ := json.Marshal(TestResponse{Id: "123", Name: "test"}) + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + assert.Equal(t, "https://api.example.com/api/v2/things", path) + assert.Equal(t, MethodPost, method) + assert.Equal(t, "Bearer test-token", headerParams["Authorization"]) + assert.Equal(t, "application/json", headerParams["Content-Type"]) + assert.Equal(t, "custom-value", headerParams["X-Custom"]) + assert.Nil(t, queryParams) // query params encoded in path + return &platformclientv2.APIResponse{ + RawBody: expectedBody, + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + result, resp, err := Do[TestResponse](context.Background(), client, MethodPost, "/api/v2/things", map[string]string{"key": "val"}, nil) + + assert.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + assert.Equal(t, "123", result.Id) + assert.Equal(t, "test", result.Name) +} + +func TestDoCallAPIError(t *testing.T) { + type TestResponse struct{} + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return nil, fmt.Errorf("connection refused") + } + + client := newTestClient(mockFn) + result, _, err := Do[TestResponse](context.Background(), client, MethodGet, "/api/v2/things", nil, nil) + + assert.Error(t, err) + assert.Nil(t, result) + assert.Contains(t, err.Error(), "connection refused") +} + +func TestDoResponseError(t *testing.T) { + type TestResponse struct{} + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return &platformclientv2.APIResponse{ + StatusCode: 404, + Error: &platformclientv2.APIError{Message: "not found"}, + ErrorMessage: "Resource not found", + }, nil + } + + client := newTestClient(mockFn) + result, resp, err := Do[TestResponse](context.Background(), client, MethodGet, "/api/v2/things/123", nil, nil) + + assert.Error(t, err) + assert.Nil(t, result) + assert.Equal(t, 404, resp.StatusCode) + assert.Contains(t, err.Error(), "Resource not found") +} + +func TestDoUnmarshalError(t *testing.T) { + type TestResponse struct { + Id int `json:"id"` + } + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return &platformclientv2.APIResponse{ + RawBody: []byte(`not valid json`), + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + result, _, err := Do[TestResponse](context.Background(), client, MethodGet, "/api/v2/things", nil, nil) + + assert.Error(t, err) + assert.Nil(t, result) +} + +func TestDoQueryParams(t *testing.T) { + type TestResponse struct{} + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + // Query params are encoded in the path, not passed separately + assert.Nil(t, queryParams) + assert.Contains(t, path, "pageSize=100") + assert.Contains(t, path, "pageNumber=1") + return &platformclientv2.APIResponse{ + RawBody: []byte(`{}`), + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + params := url.Values{"pageSize": {"100"}, "pageNumber": {"1"}} + _, _, err := Do[TestResponse](context.Background(), client, MethodGet, "/api/v2/things", nil, params) + + assert.NoError(t, err) +} + +func TestDoMultiValueQueryParams(t *testing.T) { + type TestResponse struct{} + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + assert.Nil(t, queryParams) + assert.Contains(t, path, "type=inboundcall") + assert.Contains(t, path, "type=outboundcall") + return &platformclientv2.APIResponse{ + RawBody: []byte(`{}`), + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + params := url.Values{"type": {"inboundcall", "outboundcall"}} + _, _, err := Do[TestResponse](context.Background(), client, MethodGet, "/api/v2/flows", nil, params) + + assert.NoError(t, err) +} + +func TestDoNoResponseSuccess(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + assert.Equal(t, "https://api.example.com/api/v2/things/123", path) + assert.Equal(t, MethodDelete, method) + return &platformclientv2.APIResponse{StatusCode: 204}, nil + } + + client := newTestClient(mockFn) + resp, err := DoNoResponse(context.Background(), client, MethodDelete, "/api/v2/things/123", nil, nil) + + assert.NoError(t, err) + assert.Equal(t, 204, resp.StatusCode) +} + +func TestDoNoResponseCallAPIError(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return nil, fmt.Errorf("timeout") + } + + client := newTestClient(mockFn) + _, err := DoNoResponse(context.Background(), client, MethodDelete, "/api/v2/things/123", nil, nil) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "timeout") +} + +func TestDoNoResponseResponseError(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return &platformclientv2.APIResponse{ + StatusCode: 409, + Error: &platformclientv2.APIError{Message: "conflict"}, + ErrorMessage: "Version conflict", + }, nil + } + + client := newTestClient(mockFn) + resp, err := DoNoResponse(context.Background(), client, MethodDelete, "/api/v2/things/123", nil, nil) + + assert.Error(t, err) + assert.Equal(t, 409, resp.StatusCode) + assert.Contains(t, err.Error(), "Version conflict") +} + +func TestDoRawSuccess(t *testing.T) { + expectedJSON := []byte(`{"id":"123","deleted":true}`) + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return &platformclientv2.APIResponse{ + RawBody: expectedJSON, + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + raw, resp, err := DoRaw(context.Background(), client, MethodGet, "/api/v2/things/123", nil, nil) + + assert.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + assert.Equal(t, expectedJSON, raw) +} + +func TestDoRawCallAPIError(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return nil, fmt.Errorf("network error") + } + + client := newTestClient(mockFn) + raw, _, err := DoRaw(context.Background(), client, MethodGet, "/api/v2/things/123", nil, nil) + + assert.Error(t, err) + assert.Nil(t, raw) +} + +func TestDoRawResponseError(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return &platformclientv2.APIResponse{ + StatusCode: 500, + Error: &platformclientv2.APIError{Message: "server error"}, + ErrorMessage: "Internal server error", + }, nil + } + + client := newTestClient(mockFn) + raw, resp, err := DoRaw(context.Background(), client, MethodGet, "/api/v2/things/123", nil, nil) + + assert.Error(t, err) + assert.Nil(t, raw) + assert.Equal(t, 500, resp.StatusCode) +} + +func TestDoWithAcceptHeaderSuccess(t *testing.T) { + expectedBody := []byte(`template content here`) + + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + assert.Equal(t, "*/*", headerParams["Accept"]) + assert.Equal(t, "application/json", headerParams["Content-Type"]) + return &platformclientv2.APIResponse{ + RawBody: expectedBody, + StatusCode: 200, + }, nil + } + + client := newTestClient(mockFn) + raw, resp, err := DoWithAcceptHeader(context.Background(), client, MethodGet, "/api/v2/things/123/template", nil, nil, "*/*") + + assert.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + assert.Equal(t, expectedBody, raw) +} + +func TestDoWithAcceptHeaderCallAPIError(t *testing.T) { + mockFn := func(path, method string, postBody interface{}, headerParams, queryParams map[string]string, formParams url.Values, fileName string, fileBytes []byte, pathName string) (*platformclientv2.APIResponse, error) { + return nil, fmt.Errorf("connection error") + } + + client := newTestClient(mockFn) + raw, _, err := DoWithAcceptHeader(context.Background(), client, MethodGet, "/api/v2/things/123/template", nil, nil, "*/*") + + assert.Error(t, err) + assert.Nil(t, raw) +} + +func TestConstants(t *testing.T) { + assert.Equal(t, "GET", MethodGet) + assert.Equal(t, "POST", MethodPost) + assert.Equal(t, "PUT", MethodPut) + assert.Equal(t, "PATCH", MethodPatch) + assert.Equal(t, "DELETE", MethodDelete) +} diff --git a/genesyscloud/external_user/genesyscloud_externalusers_identity_proxy.go b/genesyscloud/external_user/genesyscloud_externalusers_identity_proxy.go index 9ba2ed306..20a8bfd3d 100644 --- a/genesyscloud/external_user/genesyscloud_externalusers_identity_proxy.go +++ b/genesyscloud/external_user/genesyscloud_externalusers_identity_proxy.go @@ -2,12 +2,10 @@ package external_user import ( "context" - "encoding/json" - "errors" "fmt" "net/url" - "strings" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" @@ -133,59 +131,7 @@ func deleteExternalUserIdentityFn(ctx context.Context, p *externalUserIdentityPr } func callExternalUserAPI(userApi *platformclientv2.UsersApi, userId string, externalUser platformclientv2.Userexternalidentifier) (*platformclientv2.Userexternalidentifier, *platformclientv2.APIResponse, error) { - var httpMethod = "POST" - path := userApi.Configuration.BasePath + "/api/v2/users/{userId}/externalid" - path = strings.Replace(path, "{userId}", url.PathEscape(fmt.Sprintf("%v", userId)), -1) - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - formParams := url.Values{} - var postBody interface{} - var postFileName string - var fileBytes []byte - - if userApi.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + userApi.Configuration.AccessToken - } - for key := range userApi.Configuration.DefaultHeader { - headerParams[key] = userApi.Configuration.DefaultHeader[key] - } - - correctedQueryParams := make(map[string]string) - for k, v := range queryParams { - if k == "varType" { - correctedQueryParams["type"] = v - continue - } - correctedQueryParams[k] = v - } - queryParams = correctedQueryParams - - localVarHttpContentTypes := []string{"application/json"} - - localVarHttpContentType := userApi.Configuration.APIClient.SelectHeaderContentType(localVarHttpContentTypes) - if localVarHttpContentType != "" { - headerParams["Content-Type"] = localVarHttpContentType - } - localVarHttpHeaderAccepts := []string{ - "application/json", - } - - localVarHttpHeaderAccept := userApi.Configuration.APIClient.SelectHeaderAccept(localVarHttpHeaderAccepts) - if localVarHttpHeaderAccept != "" { - headerParams["Accept"] = localVarHttpHeaderAccept - } - postBody = &externalUser - - var successPayload *platformclientv2.Userexternalidentifier - response, err := userApi.Configuration.APIClient.CallAPI(path, httpMethod, postBody, headerParams, queryParams, formParams, postFileName, fileBytes, "other") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if err == nil && response.Error != nil { - err = errors.New(response.ErrorMessage) - } else if response.HasBody { - json.Unmarshal(response.RawBody, &successPayload) - - } - return successPayload, response, err + c := customapi.NewClient(userApi.Configuration, ResourceType) + path := "/api/v2/users/" + url.PathEscape(userId) + "/externalid" + return customapi.Do[platformclientv2.Userexternalidentifier](context.Background(), c, customapi.MethodPost, path, &externalUser, nil) } diff --git a/genesyscloud/guide/genesyscloud_guide_proxy.go b/genesyscloud/guide/genesyscloud_guide_proxy.go index fcb785cf6..483861100 100644 --- a/genesyscloud/guide/genesyscloud_guide_proxy.go +++ b/genesyscloud/guide/genesyscloud_guide_proxy.go @@ -3,10 +3,8 @@ package guide import ( "context" "fmt" - "io" - "net/http" - "net/url" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" @@ -25,6 +23,7 @@ type getDeleteJobStatusByIdFunc func(ctx context.Context, p *guideProxy, id stri type guideProxy struct { clientConfig *platformclientv2.Configuration + customApiClient *customapi.Client getAllGuidesAttr getAllGuidesFunc createGuideAttr createGuideFunc getGuideByIdAttr getGuideByIdFunc @@ -38,6 +37,7 @@ func newGuideProxy(clientConfig *platformclientv2.Configuration) *guideProxy { guideCache := rc.NewResourceCache[Guide]() return &guideProxy{ clientConfig: clientConfig, + customApiClient: customapi.NewClient(clientConfig, ResourceType), getAllGuidesAttr: getAllGuidesFn, createGuideAttr: createGuideFn, getGuideByIdAttr: getGuideByIdFn, @@ -83,92 +83,51 @@ func getAllGuidesFn(ctx context.Context, p *guideProxy, name string) (*[]Guide, } func sdkGetAllGuidesFn(ctx context.Context, p *guideProxy, name string) (*[]Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - client := &http.Client{} - action := http.MethodGet - baseURL := p.clientConfig.BasePath + "/api/v2/guides" var allGuides []Guide - - u, err := url.Parse(baseURL) - if err != nil { - return nil, nil, fmt.Errorf("error parsing URL: %v", err) - } - - q := u.Query() + queryParams := customapi.NewQueryParams(map[string]string{"pageSize": "100", "pageNumber": "1"}) if name != "" { - q.Add("name", name) - } - q.Add("pageSize", "100") - q.Add("pageNumber", "1") - - u.RawQuery = q.Encode() - - req, err := createHTTPRequest(ctx, action, u.String(), nil, p) - if err != nil { - return nil, nil, err + queryParams.Set("name", name) } - body, resp, err := callAPI(ctx, client, req) + guides, resp, err := customapi.Do[GuideEntityListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides", nil, queryParams) if err != nil { return nil, resp, err } - - var guides GuideEntityListing - if err := unmarshalResponse(body, &guides); err != nil { - return nil, resp, err - } - if guides.Entities == nil { return &allGuides, resp, nil } - allGuides = append(allGuides, *guides.Entities...) for pageNum := 2; pageNum <= *guides.PageCount; pageNum++ { - q.Set("pageNumber", fmt.Sprintf("%v", pageNum)) - req.URL.RawQuery = q.Encode() - - body, resp, err = callAPI(ctx, client, req) + queryParams.Set("pageNumber", fmt.Sprintf("%v", pageNum)) + pageGuides, resp, err := customapi.Do[GuideEntityListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides", nil, queryParams) if err != nil { return nil, resp, err } - - var respBody GuideEntityListing - if err := unmarshalResponse(body, &respBody); err != nil { - return nil, resp, err - } - - if respBody.Entities != nil { - allGuides = append(allGuides, *respBody.Entities...) + if pageGuides.Entities != nil { + allGuides = append(allGuides, *pageGuides.Entities...) } } for _, guide := range allGuides { rc.SetCache(p.guideCache, *guide.Id, guide) } - return &allGuides, resp, nil } func createGuideFn(ctx context.Context, p *guideProxy, guide *CreateGuide) (*Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - baseURL := p.clientConfig.BasePath + "/api/v2/guides" - return makeAPIRequest[Guide](ctx, http.MethodPost, baseURL, guide, p) + return customapi.Do[Guide](ctx, p.customApiClient, customapi.MethodPost, "/api/v2/guides", guide, nil) } func getGuideByIdFn(ctx context.Context, p *guideProxy, id string) (*Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - if guide := rc.GetCacheItem(p.guideCache, id); guide != nil { return guide, nil, nil } - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + id - return makeAPIRequest[Guide](ctx, http.MethodGet, baseURL, nil, p) + return customapi.Do[Guide](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides/"+id, nil, nil) } func getGuideByNameFn(ctx context.Context, p *guideProxy, name string) (string, bool, *platformclientv2.APIResponse, error) { @@ -197,11 +156,8 @@ func getGuideByNameFn(ctx context.Context, p *guideProxy, name string) (string, } func deleteGuideFn(ctx context.Context, p *guideProxy, id string) (*DeleteObjectJob, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + id + "/jobs" - jobResponse, resp, err := makeAPIRequest[DeleteObjectJob](ctx, http.MethodDelete, baseURL, nil, p) + jobResponse, resp, err := customapi.Do[DeleteObjectJob](ctx, p.customApiClient, customapi.MethodDelete, "/api/v2/guides/"+id+"/jobs", nil, nil) if err != nil { return nil, resp, err } @@ -210,79 +166,14 @@ func deleteGuideFn(ctx context.Context, p *guideProxy, id string) (*DeleteObject } func getDeleteJobStatusByIdFn(ctx context.Context, p *guideProxy, jobId string, guideId string) (*DeleteObjectJob, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + guideId + "/jobs/" + jobId - jobResponse, resp, err := makeAPIRequest[DeleteObjectJob](ctx, http.MethodGet, baseURL, nil, p) + jobResponse, resp, err := customapi.Do[DeleteObjectJob](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides/"+guideId+"/jobs/"+jobId, nil, nil) if err != nil { return nil, resp, err } jobResponse.GuideId = guideId - if jobResponse.Status == "Succeeded" { rc.DeleteCacheItem(p.guideCache, guideId) } - return jobResponse, resp, nil } - -// makeAPIRequest performs a complete API request for any of the guide endpoints -func makeAPIRequest[T any](ctx context.Context, method, url string, requestBody interface{}, p *guideProxy) (*T, *platformclientv2.APIResponse, error) { - var req *http.Request - var err error - - // Set resource context for SDK debug logging before creating HTTP request - ctx = provider.EnsureResourceContext(ctx, ResourceType) - - if requestBody != nil { - req, err = marshalAndCreateRequest(ctx, method, url, requestBody, p) - } else { - req, err = createHTTPRequest(ctx, method, url, nil, p) - } - - if err != nil { - return nil, nil, err - } - - client := &http.Client{} - respBody, resp, err := callAPI(ctx, client, req) - if err != nil { - return nil, resp, err - } - - var result T - if err := unmarshalResponse(respBody, &result); err != nil { - return nil, resp, err - } - - return &result, resp, nil -} - -// callAPI is a helper function which will be removed when the endpoints are public -func callAPI(ctx context.Context, client *http.Client, req *http.Request) ([]byte, *platformclientv2.APIResponse, error) { - ctx = provider.EnsureResourceContext(ctx, ResourceType) - req = req.WithContext(ctx) - - resp, err := client.Do(req) - if err != nil { - return nil, nil, fmt.Errorf("error making request: %v", err) - } - defer resp.Body.Close() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, nil, fmt.Errorf("error reading response: %v", err) - } - - response := &platformclientv2.APIResponse{ - StatusCode: resp.StatusCode, - Response: resp, - } - - if resp.StatusCode >= 400 { - return nil, response, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody)) - } - - return respBody, response, nil -} diff --git a/genesyscloud/guide/resource_genesyscloud_guide_utils.go b/genesyscloud/guide/resource_genesyscloud_guide_utils.go index af45fd4ec..4849470bf 100644 --- a/genesyscloud/guide/resource_genesyscloud_guide_utils.go +++ b/genesyscloud/guide/resource_genesyscloud_guide_utils.go @@ -1,14 +1,11 @@ package guide import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "log" - "net/http" - "net/url" + + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" ) @@ -25,72 +22,16 @@ func GenerateGuideResource(resourceID string, name string) string { // Achieved by a GET request to the guides endpoint, checking if the status code is 5xx func GuideFtIsEnabled() bool { clientConfig := platformclientv2.GetDefaultConfiguration() - client := &http.Client{} - baseURL := clientConfig.BasePath + "/api/v2/guides" - - u, err := url.Parse(baseURL) - if err != nil { - log.Printf("Error parsing URL: %v", err) - return false - } - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - log.Printf("Error creating request: %v", err) - return false - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "Bearer "+clientConfig.AccessToken) - - ctx := context.Background() - resp, err := client.Do(req.WithContext(ctx)) + c := customapi.NewClient(clientConfig, ResourceType) + _, resp, err := customapi.DoRaw(context.Background(), c, customapi.MethodGet, "/api/v2/guides", nil, nil) if err != nil { - log.Printf("Error sending request: %v", err) + if resp != nil && resp.StatusCode < 500 { + return true + } + log.Printf("Error checking guide feature toggle: %v", err) return false } - - defer resp.Body.Close() - - return resp.StatusCode < 500 -} - -// setRequestHeader sets the request header for the guide proxy -func setRequestHeader(r *http.Request, p *guideProxy) *http.Request { - r.Header.Set("Content-Type", "application/json") - r.Header.Set("Accept", "application/json") - r.Header.Set("Authorization", "Bearer "+p.clientConfig.AccessToken) - return r -} - -// createHTTPRequest creates a new HTTP request with proper headers -func createHTTPRequest(ctx context.Context, method, url string, body io.Reader, p *guideProxy) (*http.Request, error) { - // Set resource context for SDK debug logging before creating HTTP request - - req, err := http.NewRequestWithContext(ctx, method, url, body) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - req = setRequestHeader(req, p) - return req, nil -} - -// marshalAndCreateRequest marshals a body to JSON and creates an HTTP request -func marshalAndCreateRequest(ctx context.Context, method, url string, body interface{}, p *guideProxy) (*http.Request, error) { - jsonBody, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("error marshaling request body: %v", err) - } - return createHTTPRequest(ctx, method, url, bytes.NewBuffer(jsonBody), p) -} - -// unmarshalResponse unmarshals a JSON response into the target struct -func unmarshalResponse(respBody []byte, target interface{}) error { - if err := json.Unmarshal(respBody, target); err != nil { - return fmt.Errorf("error unmarshaling response: %v", err) - } - return nil + return true } // Structs diff --git a/genesyscloud/guide_version/genesyscloud_guide_version_proxy.go b/genesyscloud/guide_version/genesyscloud_guide_version_proxy.go index 1cbebd9ba..27261762e 100644 --- a/genesyscloud/guide_version/genesyscloud_guide_version_proxy.go +++ b/genesyscloud/guide_version/genesyscloud_guide_version_proxy.go @@ -1,16 +1,12 @@ package guide_version import ( - "bytes" "context" "encoding/json" "fmt" - "io" "log" - "net/http" - "net/url" - "time" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" @@ -27,6 +23,7 @@ type getGuideVersionPublishJobStatusFunc func(ctx context.Context, p *guideVersi type getGuideByIdFunc func(ctx context.Context, p *guideVersionProxy, id string) (*Guide, *platformclientv2.APIResponse, error) type guideVersionProxy struct { clientConfig *platformclientv2.Configuration + customApiClient *customapi.Client GetAllGuidesAttr GetAllGuidesFunc createGuideVersionAttr createGuideVersionFunc getGuideVersionByIdAttr getGuideVersionByIdFunc @@ -39,6 +36,7 @@ type guideVersionProxy struct { func newGuideVersionProxy(clientConfig *platformclientv2.Configuration) *guideVersionProxy { return &guideVersionProxy{ clientConfig: clientConfig, + customApiClient: customapi.NewClient(clientConfig, ResourceType), GetAllGuidesAttr: GetAllGuidesFn, createGuideVersionAttr: createGuideVersionFn, getGuideVersionByIdAttr: getGuideVersionByIdFn, @@ -80,78 +78,36 @@ func (p *guideVersionProxy) getGuideById(ctx context.Context, id string) (*Guide // GetAll Functions func GetAllGuidesFn(ctx context.Context, p *guideVersionProxy) (*[]Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkGetAllGuides(ctx, p) } func sdkGetAllGuides(ctx context.Context, p *guideVersionProxy) (*[]Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodGet - baseURL := p.clientConfig.BasePath + "/api/v2/guides" var allGuides []Guide + queryParams := customapi.NewQueryParams(map[string]string{"pageSize": "100", "pageNumber": "1"}) - u, err := url.Parse(baseURL) - if err != nil { - return nil, nil, fmt.Errorf("error parsing URL: %v", err) - } - - q := u.Query() - q.Add("pageSize", "100") - q.Add("pageNumber", "1") - - u.RawQuery = q.Encode() - - req, err := http.NewRequestWithContext(ctx, action, u.String(), nil) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - body, resp, err := callAPI(ctx, client, req) + guides, resp, err := customapi.Do[GuideEntityListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides", nil, queryParams) if err != nil { return nil, resp, err } - - var guides GuideEntityListing - if err := json.Unmarshal([]byte(body), &guides); err != nil { - return nil, resp, fmt.Errorf("error unmarshaling response: %v", err) - } - if guides.Entities == nil { return &allGuides, resp, nil } - allGuides = append(allGuides, *guides.Entities...) if guides.PageCount != nil && *guides.PageCount > 1 { for pageNum := 2; pageNum <= *guides.PageCount; pageNum++ { - q.Set("pageNumber", fmt.Sprintf("%v", pageNum)) - req.URL.RawQuery = q.Encode() - - body, resp, err = callAPI(ctx, client, req) + queryParams.Set("pageNumber", fmt.Sprintf("%v", pageNum)) + pageGuides, _, err := customapi.Do[GuideEntityListing](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides", nil, queryParams) if err != nil { return nil, resp, fmt.Errorf("error fetching page %d: %v", pageNum, err) } - - var respBody GuideEntityListing - if err := json.Unmarshal([]byte(body), &respBody); err != nil { - return nil, resp, fmt.Errorf("error unmarshaling response for page %d: %v", pageNum, err) - } - - if respBody.Entities != nil { - allGuides = append(allGuides, *respBody.Entities...) + if pageGuides.Entities != nil { + allGuides = append(allGuides, *pageGuides.Entities...) } } } - log.Printf("Successfully retrieved %d guides", len(allGuides)) return &allGuides, resp, nil } @@ -159,285 +115,83 @@ func sdkGetAllGuides(ctx context.Context, p *guideVersionProxy) (*[]Guide, *plat // Create Functions func createGuideVersionFn(ctx context.Context, p *guideVersionProxy, guideVersion *CreateGuideVersionRequest, guideId string) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkPostGuideVersion(ctx, guideVersion, p, guideId) } func sdkPostGuideVersion(ctx context.Context, body *CreateGuideVersionRequest, p *guideVersionProxy, guideId string) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodPost - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + guideId + "/versions" - - jsonBody, err := json.Marshal(body) - if err != nil { - return nil, nil, fmt.Errorf("error marshaling guide version: %v", err) - } - - req, err := http.NewRequestWithContext(ctx, action, baseURL, bytes.NewBuffer(jsonBody)) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) - } - - var guideVersion VersionResponse - if err := json.Unmarshal(respBody, &guideVersion); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v", err) - } - - return &guideVersion, apiResp, nil + return customapi.Do[VersionResponse](ctx, p.customApiClient, customapi.MethodPost, "/api/v2/guides/"+guideId+"/versions", body, nil) } // Read Functions func getGuideVersionByIdFn(ctx context.Context, p *guideVersionProxy, id string, guideId string) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkGetGuideVersionById(ctx, p, id, guideId) } func sdkGetGuideVersionById(ctx context.Context, p *guideVersionProxy, id string, guideId string) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodGet - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + guideId + "/versions/" + id - - req, err := http.NewRequestWithContext(ctx, action, baseURL, nil) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) - } - - var guideVersion VersionResponse - if err := json.Unmarshal(respBody, &guideVersion); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v", err) - } - - return &guideVersion, apiResp, nil + return customapi.Do[VersionResponse](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides/"+guideId+"/versions/"+id, nil, nil) } // Update Functions func updateGuideVersionFn(ctx context.Context, p *guideVersionProxy, id string, guideId string, guideVersion *UpdateGuideVersion) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkUpdateGuideVersion(ctx, p, id, guideId, guideVersion) } func sdkUpdateGuideVersion(ctx context.Context, p *guideVersionProxy, id string, guideId string, body *UpdateGuideVersion) (*VersionResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodPatch - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + guideId + "/versions/" + id - - jsonBody, err := json.Marshal(body) - if err != nil { - return nil, nil, fmt.Errorf("error marshaling guide version: %v", err) - } - - req, err := http.NewRequestWithContext(ctx, action, baseURL, bytes.NewBuffer(jsonBody)) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) - } - - var guideVersion VersionResponse - if err := json.Unmarshal(respBody, &guideVersion); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v", err) - } - - return &guideVersion, apiResp, nil -} - -// Helper api call function to be removed once endpoints are public -func callAPI(ctx context.Context, client *http.Client, req *http.Request) ([]byte, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before making HTTP request - ctx = provider.EnsureResourceContext(ctx, ResourceType) - - // Attach context to the request - req = req.WithContext(ctx) - - resp, err := client.Do(req) - if err != nil { - return nil, nil, fmt.Errorf("error making request: %v", err) - } - defer resp.Body.Close() - - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, nil, fmt.Errorf("error reading response: %v", err) - } - - apiResp := &platformclientv2.APIResponse{ - StatusCode: resp.StatusCode, - Response: resp, - } - - if resp.StatusCode >= 400 { - return nil, apiResp, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody)) - } - - return respBody, apiResp, nil + return customapi.Do[VersionResponse](ctx, p.customApiClient, customapi.MethodPatch, "/api/v2/guides/"+guideId+"/versions/"+id, body, nil) } // Functions to publish the guide version func publishGuideVersionFn(ctx context.Context, p *guideVersionProxy, body *GuideVersionPublishJobRequest) (*VersionJobResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkPublishGuideVersion(ctx, p, body) } func sdkPublishGuideVersion(ctx context.Context, p *guideVersionProxy, body *GuideVersionPublishJobRequest) (*VersionJobResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodPost - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + body.GuideId + "/versions/" + body.VersionId + "/jobs" - - jsonBody, err := json.Marshal(body) + path := "/api/v2/guides/" + body.GuideId + "/versions/" + body.VersionId + "/jobs" + rawBody, resp, err := customapi.DoRaw(ctx, p.customApiClient, customapi.MethodPost, path, body, nil) if err != nil { - return nil, nil, fmt.Errorf("error marshaling guide version: %v", err) - } - - req, err := http.NewRequestWithContext(ctx, action, baseURL, bytes.NewBuffer(jsonBody)) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) + return nil, resp, err } - - if apiResp.StatusCode == 202 { - if len(respBody) == 0 { - log.Println("Received 202 with empty body - job started successfully") - return nil, apiResp, nil - } + if resp.StatusCode == 202 && len(rawBody) == 0 { + log.Println("Received 202 with empty body - job started successfully") + return nil, resp, nil } - - if len(respBody) == 0 { - return nil, apiResp, fmt.Errorf("empty response body") + if len(rawBody) == 0 { + return nil, resp, fmt.Errorf("empty response body") } - var jobResponse VersionJobResponse - if err := json.Unmarshal(respBody, &jobResponse); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v, body: %s", err, string(respBody)) + if err := json.Unmarshal(rawBody, &jobResponse); err != nil { + return nil, resp, fmt.Errorf("error unmarshaling response: %v, body: %s", err, string(rawBody)) } - - return &jobResponse, apiResp, nil + return &jobResponse, resp, nil } func getGuideVersionPublishJobStatusFn(ctx context.Context, p *guideVersionProxy, versionId, jobId, guideId string) (*VersionJobResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkGetGuideVersionPublishJobStatus(ctx, p, versionId, jobId, guideId) } func sdkGetGuideVersionPublishJobStatus(ctx context.Context, p *guideVersionProxy, versionId, jobId string, guideId string) (*VersionJobResponse, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodGet - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + guideId + "/versions/" + versionId + "/jobs/" + jobId - - req, err := http.NewRequestWithContext(ctx, action, baseURL, nil) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) - } - - var guideVersion VersionJobResponse - if err := json.Unmarshal(respBody, &guideVersion); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v", err) - } - - return &guideVersion, apiResp, nil + return customapi.Do[VersionJobResponse](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides/"+guideId+"/versions/"+versionId+"/jobs/"+jobId, nil, nil) } func getGuideByIdFn(ctx context.Context, p *guideVersionProxy, id string) (*Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - return sdkGetGuideById(ctx, p, id) } + func sdkGetGuideById(ctx context.Context, p *guideVersionProxy, id string) (*Guide, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before creating HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - client := &http.Client{ - Timeout: 30 * time.Second, - } - action := http.MethodGet - baseURL := p.clientConfig.BasePath + "/api/v2/guides/" + id - - req, err := http.NewRequestWithContext(ctx, action, baseURL, nil) - if err != nil { - return nil, nil, fmt.Errorf("error creating request: %v", err) - } - - req = buildRequestHeader(req, p) - - respBody, apiResp, err := callAPI(ctx, client, req) - if err != nil { - return nil, apiResp, fmt.Errorf("error calling API: %v", err) - } - - var guide Guide - if err := json.Unmarshal(respBody, &guide); err != nil { - return nil, apiResp, fmt.Errorf("error unmarshaling response: %v", err) - } - - return &guide, apiResp, nil + return customapi.Do[Guide](ctx, p.customApiClient, customapi.MethodGet, "/api/v2/guides/"+id, nil, nil) } diff --git a/genesyscloud/guide_version/resource_genesyscloud_guide_version_utils.go b/genesyscloud/guide_version/resource_genesyscloud_guide_version_utils.go index 8b909c1bc..918cbfd6a 100644 --- a/genesyscloud/guide_version/resource_genesyscloud_guide_version_utils.go +++ b/genesyscloud/guide_version/resource_genesyscloud_guide_version_utils.go @@ -3,7 +3,6 @@ package guide_version import ( "fmt" "log" - "net/http" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -25,12 +24,6 @@ func parseId(id string) (string, string, error) { return guideId, versionId, nil } -func buildRequestHeader(r *http.Request, p *guideVersionProxy) *http.Request { - r.Header.Set("Content-Type", "application/json") - r.Header.Set("Authorization", "Bearer "+p.clientConfig.AccessToken) - return r -} - func buildGuideVersionFromResourceData(d *schema.ResourceData) *CreateGuideVersionRequest { log.Printf("Building Guide Version from Resource Data") diff --git a/genesyscloud/integration_action/genesyscloud_integration_action_proxy.go b/genesyscloud/integration_action/genesyscloud_integration_action_proxy.go index 907992136..3b7404851 100644 --- a/genesyscloud/integration_action/genesyscloud_integration_action_proxy.go +++ b/genesyscloud/integration_action/genesyscloud_integration_action_proxy.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "log" @@ -14,6 +13,8 @@ import ( "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/files" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" + "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" ) @@ -72,6 +73,7 @@ type publishIntegrationActionDraftFunc func(ctx context.Context, p *integrationA type integrationActionsProxy struct { clientConfig *platformclientv2.Configuration integrationsApi *platformclientv2.IntegrationsApi + customApiClient *customapi.Client getAllIntegrationActionsAttr getAllIntegrationActionsFunc createIntegrationActionAttr createIntegrationActionFunc getIntegrationActionByIdAttr getIntegrationActionByIdFunc @@ -94,6 +96,7 @@ func newIntegrationActionsProxy(clientConfig *platformclientv2.Configuration) *i return &integrationActionsProxy{ clientConfig: clientConfig, integrationsApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), getAllIntegrationActionsAttr: getAllIntegrationActionsFn, createIntegrationActionAttr: createIntegrationActionFn, createIntegrationActionDraftAttr: createIntegrationActionDraftFn, @@ -570,144 +573,34 @@ func getIntegrationActionTemplateFn(ctx context.Context, p *integrationActionsPr // sdkPostIntegrationAction is the non-sdk helper method for creating an Integration Action func sdkPostIntegrationAction(ctx context.Context, body *IntegrationAction, api *platformclientv2.IntegrationsApi) (*IntegrationAction, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before making HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/integrations/actions" - - headerParams := make(map[string]string) - - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *IntegrationAction - response, err := apiClient.CallAPI(path, http.MethodPost, body, headerParams, nil, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - return successPayload, response, err + c := customapi.NewClient(api.Configuration, ResourceType) + return customapi.Do[IntegrationAction](ctx, c, customapi.MethodPost, "/api/v2/integrations/actions", body, nil) } // sdkGetIntegrationAction is the non-sdk helper method for getting an Integration Action func sdkGetIntegrationAction(ctx context.Context, actionId string, api *platformclientv2.IntegrationsApi) (*IntegrationAction, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before making HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/integrations/actions/" + actionId - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - queryParams["expand"] = "contract" - queryParams["includeConfig"] = "true" - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *IntegrationAction - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - return successPayload, response, err + c := customapi.NewClient(api.Configuration, ResourceType) + queryParams := customapi.NewQueryParams(map[string]string{"expand": "contract", "includeConfig": "true"}) + return customapi.Do[IntegrationAction](ctx, c, customapi.MethodGet, "/api/v2/integrations/actions/"+actionId, nil, queryParams) } // sdkPostIntegrationActionDraft is the non-sdk helper method for creating an Integration Action func sdkPostIntegrationActionDraft(ctx context.Context, body *IntegrationAction, api *platformclientv2.IntegrationsApi) (*IntegrationAction, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before making HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/integrations/actions/drafts" - - headerParams := make(map[string]string) - - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *IntegrationAction - response, err := apiClient.CallAPI(path, http.MethodPost, body, headerParams, nil, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - return successPayload, response, err + c := customapi.NewClient(api.Configuration, ResourceType) + return customapi.Do[IntegrationAction](ctx, c, customapi.MethodPost, "/api/v2/integrations/actions/drafts", body, nil) } // sdkGetIntegrationActionTemplate is the non-sdk helper method for getting an Integration Action Template func sdkGetIntegrationActionTemplate(ctx context.Context, actionId, templateName string, api *platformclientv2.IntegrationsApi) (*string, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging before making HTTP request ctx = provider.EnsureResourceContext(ctx, ResourceType) - - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/integrations/actions/" + actionId + "/templates/" + templateName - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "*/*" - - var successPayload *string - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") + c := customapi.NewClient(api.Configuration, ResourceType) + rawBody, resp, err := customapi.DoWithAcceptHeader(ctx, c, customapi.MethodGet, "/api/v2/integrations/actions/"+actionId+"/templates/"+templateName, nil, nil, "*/*") if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - templateStr := string(response.RawBody) - successPayload = &templateStr + return nil, resp, err } - return successPayload, response, err + templateStr := string(rawBody) + return &templateStr, resp, nil } diff --git a/genesyscloud/knowledge_document/genesyscloud_knowledge_document_proxy.go b/genesyscloud/knowledge_document/genesyscloud_knowledge_document_proxy.go index d812e0384..dde93ecb7 100644 --- a/genesyscloud/knowledge_document/genesyscloud_knowledge_document_proxy.go +++ b/genesyscloud/knowledge_document/genesyscloud_knowledge_document_proxy.go @@ -11,6 +11,8 @@ import ( resourceExporter "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_exporter" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" @@ -33,6 +35,7 @@ type updateKnowledgeKnowledgebaseDocumentFunc func(ctx context.Context, p *knowl type knowledgeDocumentProxy struct { clientConfig *platformclientv2.Configuration KnowledgeApi *platformclientv2.KnowledgeApi + customApiClient *customapi.Client getKnowledgeKnowledgebaseCategoryAttr getKnowledgeKnowledgebaseCategoryFunc getKnowledgeKnowledgebaseCategoriesAttr getKnowledgeKnowledgebaseCategoriesFunc getKnowledgeKnowledgebaseLabelsAttr getKnowledgeKnowledgebaseLabelsFunc @@ -59,6 +62,7 @@ func newKnowledgeDocumentProxy(clientConfig *platformclientv2.Configuration) *kn return &knowledgeDocumentProxy{ clientConfig: clientConfig, KnowledgeApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), getKnowledgeKnowledgebaseCategoryAttr: getKnowledgeKnowledgebaseCategoryFn, getKnowledgeKnowledgebaseCategoriesAttr: getKnowledgeKnowledgebaseCategoriesFn, getKnowledgeKnowledgebaseLabelsAttr: getKnowledgeKnowledgebaseLabelsFn, @@ -260,32 +264,26 @@ func GetAllKnowledgeDocumentEntitiesFn(ctx context.Context, p *knowledgeDocument const pageSize = 100 // prepare base url resourcePath := fmt.Sprintf("/api/v2/knowledge/knowledgebases/%s/documents", url.PathEscape(*knowledgeBase.Id)) - listDocumentsBaseUrl := fmt.Sprintf("%s%s", p.KnowledgeApi.Configuration.BasePath, resourcePath) for { // prepare query params - queryParams := make(map[string]string, 0) - queryParams["after"] = after - queryParams["pageSize"] = fmt.Sprintf("%v", pageSize) - queryParams["includeDrafts"] = "true" - - // prepare headers - headers := make(map[string]string) - headers["Authorization"] = fmt.Sprintf("Bearer %s", p.clientConfig.AccessToken) - headers["Content-Type"] = "application/json" - headers["Accept"] = "application/json" + queryParams := customapi.NewQueryParams(map[string]string{ + "after": after, + "pageSize": fmt.Sprintf("%v", pageSize), + "includeDrafts": "true", + }) // execute request - response, err := p.clientConfig.APIClient.CallAPI(listDocumentsBaseUrl, "GET", nil, headers, queryParams, nil, "", nil, "") + rawBody, resp, err := customapi.DoRaw(ctx, p.customApiClient, customapi.MethodGet, resourcePath, nil, queryParams) if err != nil { - return nil, response, fmt.Errorf("failed to read knowledge document list response error: %s", err) + return nil, resp, fmt.Errorf("failed to read knowledge document list response error: %s", err) } // process response var knowledgeDocuments platformclientv2.Knowledgedocumentresponselisting - unmarshalErr := json.Unmarshal(response.RawBody, &knowledgeDocuments) + unmarshalErr := json.Unmarshal(rawBody, &knowledgeDocuments) if unmarshalErr != nil { - return nil, response, fmt.Errorf("failed to unmarshal knowledge document list response: %s", unmarshalErr) + return nil, resp, fmt.Errorf("failed to unmarshal knowledge document list response: %s", unmarshalErr) } /** diff --git a/genesyscloud/process_automation_trigger/data_source_genesyscloud_processautomation_trigger.go b/genesyscloud/process_automation_trigger/data_source_genesyscloud_processautomation_trigger.go index f4aa8e1b1..a2fe9561f 100644 --- a/genesyscloud/process_automation_trigger/data_source_genesyscloud_processautomation_trigger.go +++ b/genesyscloud/process_automation_trigger/data_source_genesyscloud_processautomation_trigger.go @@ -2,12 +2,10 @@ package process_automation_trigger import ( "context" - "encoding/json" - "errors" "fmt" - "net/http" "time" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" @@ -39,16 +37,14 @@ func dataSourceProcessAutomationTrigger() *schema.Resource { func dataSourceProcessAutomationTriggerRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { sdkConfig := m.(*provider.ProviderMeta).ClientConfig - integrationAPI := platformclientv2.NewIntegrationsApiWithConfig(sdkConfig) triggerName := d.Get("name").(string) return util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - // create path - path := integrationAPI.Configuration.BasePath + "/api/v2/processAutomation/triggers" + relativePath := "/api/v2/processAutomation/triggers" - for pageNum := 1; ; pageNum++ { - processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(ctx, path, integrationAPI) + for { + processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(ctx, sdkConfig, relativePath) if getErr != nil { return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceType, fmt.Sprintf("failed to get page of process automation triggers: %s", getErr), resp)) @@ -69,39 +65,12 @@ func dataSourceProcessAutomationTriggerRead(ctx context.Context, d *schema.Resou return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceType, fmt.Sprintf("no process automation triggers found with name: %s", getErr), resp)) } - path = integrationAPI.Configuration.BasePath + *processAutomationTriggers.NextUri + relativePath = *processAutomationTriggers.NextUri } }) } -func getAllProcessAutomationTriggers(ctx context.Context, path string, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTriggers, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging - - apiClient := &api.Configuration.APIClient - - headerParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *ProcessAutomationTriggers - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, nil, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - - return successPayload, response, err +func getAllProcessAutomationTriggers(ctx context.Context, config *platformclientv2.Configuration, relativePath string) (*ProcessAutomationTriggers, *platformclientv2.APIResponse, error) { + c := customapi.NewClient(config, ResourceType) + return customapi.Do[ProcessAutomationTriggers](ctx, c, customapi.MethodGet, relativePath, nil, nil) } diff --git a/genesyscloud/process_automation_trigger/process_automation_triggers_proxy.go b/genesyscloud/process_automation_trigger/process_automation_triggers_proxy.go index 0a8a186e0..fa79525bb 100644 --- a/genesyscloud/process_automation_trigger/process_automation_triggers_proxy.go +++ b/genesyscloud/process_automation_trigger/process_automation_triggers_proxy.go @@ -3,11 +3,10 @@ package process_automation_trigger import ( "context" "encoding/json" - "errors" "fmt" "log" - "net/http" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" resourceExporter "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_exporter" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" @@ -17,158 +16,69 @@ import ( ) func postProcessAutomationTrigger(pat *ProcessAutomationTrigger, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) { - apiClient := &api.Configuration.APIClient - jsonStr, err := pat.toJSONString() + body, err := pat.toJSONBody() if err != nil { return nil, nil, err } - var jsonMap map[string]interface{} - json.Unmarshal([]byte(jsonStr), &jsonMap) - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers" - - // add default headers if any - headerParams := make(map[string]string) - - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *ProcessAutomationTrigger - response, err := apiClient.CallAPI(path, http.MethodPost, jsonMap, headerParams, nil, nil, "", nil, "") - + c := customapi.NewClient(api.Configuration, ResourceType) + result, resp, err := customapi.Do[ProcessAutomationTrigger](context.Background(), c, customapi.MethodPost, "/api/v2/processAutomation/triggers", body, nil) if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - log.Printf("Process automation trigger created with Id %s and correlationId: %s", *successPayload.Id, response.CorrelationID) + return nil, resp, err } - - return successPayload, response, err + log.Printf("Process automation trigger created with Id %s and correlationId: %s", *result.Id, resp.CorrelationID) + return result, resp, nil } func getProcessAutomationTrigger(triggerId string, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) { - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId - - headerParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] + c := customapi.NewClient(api.Configuration, ResourceType) + rawBody, resp, err := customapi.DoRaw(context.Background(), c, customapi.MethodGet, "/api/v2/processAutomation/triggers/"+triggerId, nil, nil) + if err != nil { + return nil, resp, err } - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, nil, nil, "", nil, "") - if response.Error != nil { - err = errors.New(response.ErrorMessage) - return nil, nil, err + // Custom unmarshaling needed to preserve matchCriteria as raw JSON + apiResp := &platformclientv2.APIResponse{ + RawBody: rawBody, + StatusCode: resp.StatusCode, + CorrelationID: resp.CorrelationID, + Response: resp.Response, } - - successPayload, err := NewProcessAutomationFromPayload(response) + result, err := NewProcessAutomationFromPayload(apiResp) if err != nil { - return nil, response, err + return nil, resp, err } - - return successPayload, response, err + return result, resp, nil } func putProcessAutomationTrigger(triggerId string, pat *ProcessAutomationTrigger, api *platformclientv2.IntegrationsApi) (*ProcessAutomationTrigger, *platformclientv2.APIResponse, error) { - apiClient := &api.Configuration.APIClient - jsonStr, err := pat.toJSONString() + body, err := pat.toJSONBody() if err != nil { return nil, nil, err } - var jsonMap map[string]interface{} - json.Unmarshal([]byte(jsonStr), &jsonMap) - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId - headerParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *ProcessAutomationTrigger - response, err := apiClient.CallAPI(path, http.MethodPut, jsonMap, headerParams, nil, nil, "", nil, "") + c := customapi.NewClient(api.Configuration, ResourceType) + result, resp, err := customapi.Do[ProcessAutomationTrigger](context.Background(), c, customapi.MethodPut, "/api/v2/processAutomation/triggers/"+triggerId, body, nil) if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - log.Printf("Process automation trigger updated with Id %s and correlationId: %s", *successPayload.Id, response.CorrelationID) + return nil, resp, err } - return successPayload, response, err + log.Printf("Process automation trigger updated with Id %s and correlationId: %s", *result.Id, resp.CorrelationID) + return result, resp, nil } func deleteProcessAutomationTrigger(triggerId string, api *platformclientv2.IntegrationsApi) (*platformclientv2.APIResponse, error) { - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/processAutomation/triggers/" + triggerId - - headerParams := make(map[string]string) - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - response, err := apiClient.CallAPI(path, http.MethodDelete, nil, headerParams, nil, nil, "", nil, "") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } - - return response, err + c := customapi.NewClient(api.Configuration, ResourceType) + return customapi.DoNoResponse(context.Background(), c, customapi.MethodDelete, "/api/v2/processAutomation/triggers/"+triggerId, nil, nil) } func getAllProcessAutomationTriggersResourceMap(ctx context.Context, clientConfig *platformclientv2.Configuration) (resourceExporter.ResourceIDMetaMap, diag.Diagnostics) { ctx = provider.EnsureResourceContext(ctx, ResourceType) resources := make(resourceExporter.ResourceIDMetaMap) - integAPI := platformclientv2.NewIntegrationsApiWithConfig(clientConfig) - // create path and map variables - path := integAPI.Configuration.BasePath + "/api/v2/processAutomation/triggers" + relativePath := "/api/v2/processAutomation/triggers" - for pageNum := 1; ; pageNum++ { - processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(ctx, path, integAPI) + for { + processAutomationTriggers, resp, getErr := getAllProcessAutomationTriggers(ctx, clientConfig, relativePath) if getErr != nil { return nil, util.BuildAPIDiagnosticError(ResourceType, fmt.Sprintf("failed to get page of process automation triggers: %v", getErr), resp) @@ -186,8 +96,19 @@ func getAllProcessAutomationTriggersResourceMap(ctx context.Context, clientConfi break } - path = integAPI.Configuration.BasePath + *processAutomationTriggers.NextUri + relativePath = *processAutomationTriggers.NextUri } return resources, nil } + +// toJSONBody converts a ProcessAutomationTrigger to a map suitable for CallAPI's postBody. +func (p *ProcessAutomationTrigger) toJSONBody() (map[string]interface{}, error) { + jsonStr, err := p.toJSONString() + if err != nil { + return nil, err + } + var jsonMap map[string]interface{} + err = json.Unmarshal([]byte(jsonStr), &jsonMap) + return jsonMap, err +} diff --git a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go index e08b9339e..5631df299 100644 --- a/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go +++ b/genesyscloud/recording_media_retention_policy/genesyscloud_recording_media_retention_policy_proxy.go @@ -2,14 +2,12 @@ package recording_media_retention_policy import ( "context" - "encoding/json" - "errors" "fmt" - "net/http" - "net/url" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" + "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" ) @@ -291,42 +289,10 @@ func getQualityFormsSurveyByNameFn(ctx context.Context, p *policyProxy, surveyNa // We need to call /api/v2/recording/mediaretentionpolicies manually to avoid setting optional boolean and integer parameters than filter results func callGetAllPoliciesApi(pageSize, pageNumber int, config *platformclientv2.Configuration) (*platformclientv2.Policyentitylisting, *platformclientv2.APIResponse, error) { - apiClient := &config.APIClient - - // create path and map variables - path := config.BasePath + "/api/v2/recording/mediaretentionpolicies" - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - formParams := url.Values{} - var postBody interface{} - var postFileName string - var postFilePath string - var fileBytes []byte - - // oauth required - if config.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + config.AccessToken - } - // add default headers if any - for key := range config.DefaultHeader { - headerParams[key] = config.DefaultHeader[key] - } - - queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "") - queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "") - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *platformclientv2.Policyentitylisting - response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes, postFilePath) - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal(response.RawBody, &successPayload) - } - return successPayload, response, err + c := customapi.NewClient(config, ResourceType) + queryParams := customapi.NewQueryParams(map[string]string{ + "pageSize": fmt.Sprintf("%d", pageSize), + "pageNumber": fmt.Sprintf("%d", pageNumber), + }) + return customapi.Do[platformclientv2.Policyentitylisting](context.Background(), c, customapi.MethodGet, "/api/v2/recording/mediaretentionpolicies", nil, queryParams) } diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go index ced437d33..63680860c 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go @@ -2,16 +2,14 @@ package routing_queue import ( "context" - "encoding/json" - "errors" "fmt" "log" "net/http" "net/url" "strconv" - "strings" "time" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" chunksProcess "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/chunks" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/lists" @@ -194,52 +192,16 @@ func updateQueueUserRingNum(queueID string, userID string, ringNum int, sdkConfi } func sdkGetRoutingQueueMembers(ctx context.Context, queueID, memberBy string, pageNumber, pageSize int, sdkConfig *platformclientv2.Configuration) (*platformclientv2.Queuememberentitylisting, *platformclientv2.APIResponse, error) { - // Set resource context for SDK debug logging - - api := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - // SDK does not support nil values for boolean query params yet, so we must manually construct this HTTP request for now - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/routing/queues/{queueId}/members" - path = strings.Replace(path, "{queueId}", queueID, -1) - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - formParams := url.Values{} - var postBody interface{} - var postFileName string - var postFilePath string - var fileBytes []byte - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "") - queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "") + c := customapi.NewClient(sdkConfig, ResourceType) + path := "/api/v2/routing/queues/" + url.PathEscape(queueID) + "/members" + queryParams := customapi.NewQueryParams(map[string]string{ + "pageSize": strconv.Itoa(pageSize), + "pageNumber": strconv.Itoa(pageNumber), + }) if memberBy != "" { - queryParams["memberBy"] = memberBy - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *platformclientv2.Queuememberentitylisting - response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes, postFilePath) - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) + queryParams.Set("memberBy", memberBy) } - return successPayload, response, err + return customapi.Do[platformclientv2.Queuememberentitylisting](ctx, c, customapi.MethodGet, path, nil, queryParams) } func checkUserMembership(queueId string, newUserIds []string, sdkConfig *platformclientv2.Configuration) error { diff --git a/genesyscloud/scripts/genesyscloud_scripts_proxy.go b/genesyscloud/scripts/genesyscloud_scripts_proxy.go index 1d9ff3300..6eb396430 100644 --- a/genesyscloud/scripts/genesyscloud_scripts_proxy.go +++ b/genesyscloud/scripts/genesyscloud_scripts_proxy.go @@ -12,6 +12,7 @@ import ( rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/constants" @@ -39,6 +40,7 @@ type getScriptByIdFunc func(ctx context.Context, p *scriptsProxy, scriptId strin type scriptsProxy struct { clientConfig *platformclientv2.Configuration scriptsApi *platformclientv2.ScriptsApi + customApiClient *customapi.Client basePath string accessToken string createScriptAttr createScriptFunc @@ -70,6 +72,7 @@ func newScriptsProxy(clientConfig *platformclientv2.Configuration) *scriptsProxy return &scriptsProxy{ clientConfig: clientConfig, scriptsApi: scriptsAPI, + customApiClient: customapi.NewClient(clientConfig, ResourceType), basePath: strings.Replace(scriptsAPI.Configuration.BasePath, "api", "apps", -1), accessToken: scriptsAPI.Configuration.AccessToken, createScriptAttr: createScriptFn, @@ -391,31 +394,16 @@ func getScriptExportUrlFn(ctx context.Context, p *scriptsProxy, scriptId string) // deleteScriptFn deletes a script from Genesys Cloud func deleteScriptFn(ctx context.Context, p *scriptsProxy, scriptId string) error { - // Set resource context for SDK debug logging ctx = provider.EnsureResourceContext(ctx, ResourceType) - - fullPath := p.scriptsApi.Configuration.BasePath + "/api/v2/scripts/" + scriptId - r, _ := http.NewRequestWithContext(ctx, http.MethodDelete, fullPath, nil) - r.Header.Set("Authorization", "Bearer "+p.scriptsApi.Configuration.AccessToken) - r.Header.Set("Content-Type", "application/json") - log.Printf("Deleting script %s", scriptId) - client := &http.Client{} - resp, err := client.Do(r) - - if resp.StatusCode == http.StatusNotFound { + resp, err := customapi.DoNoResponse(ctx, p.customApiClient, customapi.MethodDelete, "/api/v2/scripts/"+scriptId, nil, nil) + if resp != nil && resp.StatusCode == http.StatusNotFound { log.Printf("Failed to delete script '%s' because it does not exist", scriptId) return nil } - - if err != nil || (resp != nil && resp.StatusCode != http.StatusOK) { - response := "nil" - if resp != nil { - response = resp.Status - } - return fmt.Errorf("failed to delete script %s. API response: %s. Error: %v", scriptId, response, err) + if err != nil { + return fmt.Errorf("failed to delete script %s: %v", scriptId, err) } - rc.DeleteCacheItem(p.scriptCache, scriptId) log.Printf("Successfully deleted script %s", scriptId) return nil @@ -516,29 +504,18 @@ func setScriptDivision(scriptId, divisionId string, p *scriptsProxy) error { if divisionId == "" { return nil } - // Set resource context for SDK debug logging - _ = provider.EnsureResourceContext(context.Background(), ResourceType) - - apiClient := &p.scriptsApi.Configuration.APIClient - action := http.MethodPost - fullPath := p.scriptsApi.Configuration.BasePath + "/api/v2/authorization/divisions/" + divisionId + "/objects/SCRIPT" + ctx := context.Background() body := []string{scriptId} - - headerParams := make(map[string]string) - - for key := range p.scriptsApi.Configuration.DefaultHeader { - headerParams[key] = p.scriptsApi.Configuration.DefaultHeader[key] - } - headerParams["Authorization"] = "Bearer " + p.scriptsApi.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - response, err := apiClient.CallAPI(fullPath, action, body, headerParams, nil, nil, "", nil, "") - - if err != nil || response.StatusCode != http.StatusNoContent { - return fmt.Errorf("failed to set divisionId script %s: status code %d due to %s", scriptId, response.StatusCode, response.ErrorMessage) + resp, err := customapi.DoNoResponse(ctx, p.customApiClient, customapi.MethodPost, "/api/v2/authorization/divisions/"+divisionId+"/objects/SCRIPT", body, nil) + if err != nil || (resp != nil && resp.StatusCode != http.StatusNoContent) { + statusCode := 0 + errorMessage := "" + if resp != nil { + statusCode = resp.StatusCode + errorMessage = resp.ErrorMessage + } + return fmt.Errorf("failed to set divisionId script %s: status code %d due to %s", scriptId, statusCode, errorMessage) } - log.Printf("successfully set divisionId for script %s", scriptId) return nil } diff --git a/genesyscloud/task_management_workitem_schema/genesyscloud_task_management_workitem_schema_proxy.go b/genesyscloud/task_management_workitem_schema/genesyscloud_task_management_workitem_schema_proxy.go index 215962587..2c3f0fdcc 100644 --- a/genesyscloud/task_management_workitem_schema/genesyscloud_task_management_workitem_schema_proxy.go +++ b/genesyscloud/task_management_workitem_schema/genesyscloud_task_management_workitem_schema_proxy.go @@ -3,11 +3,10 @@ package task_management_workitem_schema import ( "context" "encoding/json" - "errors" "fmt" "log" - "net/http" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" @@ -36,6 +35,7 @@ type getTaskManagementWorkitemSchemaDeletedStatusFunc func(ctx context.Context, type taskManagementProxy struct { clientConfig *platformclientv2.Configuration taskManagementApi *platformclientv2.TaskManagementApi + customApiClient *customapi.Client createTaskManagementWorkitemSchemaAttr createTaskManagementWorkitemSchemaFunc getAllTaskManagementWorkitemSchemaAttr getAllTaskManagementWorkitemSchemaFunc getTaskManagementWorkitemSchemasByNameAttr getTaskManagementWorkitemSchemasByNameFunc @@ -54,6 +54,7 @@ func newTaskManagementProxy(clientConfig *platformclientv2.Configuration) *taskM return &taskManagementProxy{ clientConfig: clientConfig, taskManagementApi: api, + customApiClient: customapi.NewClient(clientConfig, ResourceType), createTaskManagementWorkitemSchemaAttr: createTaskManagementWorkitemSchemaFn, getAllTaskManagementWorkitemSchemaAttr: getAllTaskManagementWorkitemSchemaFn, getTaskManagementWorkitemSchemasByNameAttr: getTaskManagementWorkitemSchemasByNameFn, @@ -194,45 +195,16 @@ func deleteTaskManagementWorkitemSchemaFn(ctx context.Context, p *taskManagement // getTaskManagementWorkitemSchemaDeletedStatusFn is an implementation function to get the 'deleted' status of a Genesys Cloud task management workitem schema func getTaskManagementWorkitemSchemaDeletedStatusFn(ctx context.Context, p *taskManagementProxy, schemaId string) (isDeleted bool, resp *platformclientv2.APIResponse, err error) { ctx = provider.EnsureResourceContext(ctx, ResourceType) - apiClient := &p.clientConfig.APIClient - - // create path and map variables - path := p.clientConfig.BasePath + "/api/v2/taskmanagement/workitems/schemas/" + schemaId - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - - // oauth required - if p.clientConfig.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + p.clientConfig.AccessToken - } - // add default headers if any - for key := range p.clientConfig.DefaultHeader { - headerParams[key] = p.clientConfig.DefaultHeader[key] - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload map[string]interface{} - response, err := apiClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") + rawBody, resp, err := customapi.DoRaw(ctx, p.customApiClient, customapi.MethodGet, "/api/v2/taskmanagement/workitems/schemas/"+schemaId, nil, nil) if err != nil { - return false, response, fmt.Errorf("failed to get workitem schema %s: %v", schemaId, err) - } - if response.Error != nil { - return false, response, fmt.Errorf("failed to get workitem schema %s: %v", schemaId, errors.New(response.ErrorMessage)) + return false, resp, fmt.Errorf("failed to get workitem schema %s: %v", schemaId, err) } - - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - if err != nil { - return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) + var result map[string]interface{} + if err := json.Unmarshal(rawBody, &result); err != nil { + return false, resp, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) } - - // Manually query for the 'deleted' property because it is removed when - // response JSON body becomes SDK Dataschema object. - if isDeleted, ok := successPayload["deleted"].(bool); ok { - return isDeleted, response, nil + if deleted, ok := result["deleted"].(bool); ok { + return deleted, resp, nil } - - return false, response, fmt.Errorf("failed to get deleted status of %s: %v", schemaId, err) + return false, resp, fmt.Errorf("failed to get deleted status of %s", schemaId) } diff --git a/genesyscloud/task_management_worktype_status_transition/genesyscloud_task_management_worktype_status_transition_proxy.go b/genesyscloud/task_management_worktype_status_transition/genesyscloud_task_management_worktype_status_transition_proxy.go index 3492dd0b3..e4de07199 100644 --- a/genesyscloud/task_management_worktype_status_transition/genesyscloud_task_management_worktype_status_transition_proxy.go +++ b/genesyscloud/task_management_worktype_status_transition/genesyscloud_task_management_worktype_status_transition_proxy.go @@ -2,15 +2,11 @@ package task_management_worktype_status_transition import ( "context" - "encoding/json" - "errors" "fmt" - "net/url" - "strings" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" taskManagementWorktype "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/task_management_worktype" - platformUtils "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/platform" "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" ) @@ -159,93 +155,7 @@ func patchTaskManagementWorktypeStatusTransitionFn(ctx context.Context, p *taskM // patchTaskManagementWorktypeStatus is an implementation of platformclientv2.PatchTaskmanagementWorktypeStatus with one particular change: // it allows the DefaultDestinationStatus to be required and sent as a nil. This is important in order to disassociate a DefaultDestinationStatus for the DELETE process func patchTaskManagementWorktypeStatus(worktypeId string, statusId string, body *Workitemstatusupdate, a *platformclientv2.TaskManagementApi) (*platformclientv2.Workitemstatus, *platformclientv2.APIResponse, error) { - var httpMethod = "PATCH" - // create path and map variables - path := a.Configuration.BasePath + "/api/v2/taskmanagement/worktypes/{worktypeId}/statuses/{statusId}" - path = strings.Replace(path, "{worktypeId}", url.PathEscape(fmt.Sprintf("%v", worktypeId)), -1) - path = strings.Replace(path, "{statusId}", url.PathEscape(fmt.Sprintf("%v", statusId)), -1) - defaultReturn := new(platformclientv2.Workitemstatus) - if true == false { - return defaultReturn, nil, errors.New("This message brought to you by the laws of physics being broken") - } - - // verify the required parameter 'worktypeId' is set - if &worktypeId == nil { - // false - return defaultReturn, nil, errors.New("Missing required parameter 'worktypeId' when calling TaskManagementApi->PatchTaskmanagementWorktypeStatus") - } - // verify the required parameter 'statusId' is set - if &statusId == nil { - // false - return defaultReturn, nil, errors.New("Missing required parameter 'statusId' when calling TaskManagementApi->PatchTaskmanagementWorktypeStatus") - } - // verify the required parameter 'body' is set - if &body == nil { - // false - return defaultReturn, nil, errors.New("Missing required parameter 'body' when calling TaskManagementApi->PatchTaskmanagementWorktypeStatus") - } - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - formParams := url.Values{} - var postBody interface{} - var postFileName string - var fileBytes []byte - // authentication (PureCloud OAuth) required - - // oauth required - if a.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + a.Configuration.AccessToken - } - // add default headers if any - for key := range a.Configuration.DefaultHeader { - headerParams[key] = a.Configuration.DefaultHeader[key] - } - - // Find an replace keys that were altered to avoid clashes with go keywords - correctedQueryParams := make(map[string]string) - for k, v := range queryParams { - if k == "varType" { - correctedQueryParams["type"] = v - continue - } - correctedQueryParams[k] = v - } - queryParams = correctedQueryParams - - // to determine the Content-Type header - localVarHttpContentTypes := []string{"application/json"} - - // set Content-Type header - localVarHttpContentType := a.Configuration.APIClient.SelectHeaderContentType(localVarHttpContentTypes) - if localVarHttpContentType != "" { - headerParams["Content-Type"] = localVarHttpContentType - } - // to determine the Accept header - localVarHttpHeaderAccepts := []string{ - "application/json", - } - - // set Accept header - localVarHttpHeaderAccept := a.Configuration.APIClient.SelectHeaderAccept(localVarHttpHeaderAccepts) - if localVarHttpHeaderAccept != "" { - headerParams["Accept"] = localVarHttpHeaderAccept - } - // body params - postBody = &body - - var successPayload *platformclientv2.Workitemstatus - response, err := a.Configuration.APIClient.CallAPI(path, httpMethod, postBody, headerParams, queryParams, formParams, postFileName, fileBytes, "other") - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if err == nil && response.Error != nil { - err = errors.New(response.ErrorMessage) - } else if response.HasBody { - if "Workitemstatus" == "string" { - platformUtils.Copy(response.RawBody, &successPayload) - } else { - err = json.Unmarshal(response.RawBody, &successPayload) - } - } - return successPayload, response, err + c := customapi.NewClient(a.Configuration, ResourceType) + path := "/api/v2/taskmanagement/worktypes/" + worktypeId + "/statuses/" + statusId + return customapi.Do[platformclientv2.Workitemstatus](context.Background(), c, customapi.MethodPatch, path, body, nil) } diff --git a/genesyscloud/telephony_providers_edges_trunkbasesettings/genesyscloud_telephony_providers_edges_trunkbasesettings_proxy.go b/genesyscloud/telephony_providers_edges_trunkbasesettings/genesyscloud_telephony_providers_edges_trunkbasesettings_proxy.go index 846d7d8d2..258c320a2 100644 --- a/genesyscloud/telephony_providers_edges_trunkbasesettings/genesyscloud_telephony_providers_edges_trunkbasesettings_proxy.go +++ b/genesyscloud/telephony_providers_edges_trunkbasesettings/genesyscloud_telephony_providers_edges_trunkbasesettings_proxy.go @@ -2,13 +2,13 @@ package telephony_providers_edges_trunkbasesettings import ( "context" - "encoding/json" - "errors" - "net/http" + "fmt" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" rc "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_cache" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" + "github.com/mypurecloud/platform-client-sdk-go/v179/platformclientv2" ) @@ -26,6 +26,7 @@ type trunkbaseSettingProxy struct { clientConfig *platformclientv2.Configuration edgesApi *platformclientv2.TelephonyProvidersEdgeApi + customApiClient *customapi.Client getTrunkBaseSettingByIdAttr getTrunkBaseSettingByIdFunc getAllTrunkBaseSettingsAttr getAllTrunkBaseSettingsFunc createTrunkBaseSettingAttr createTrunkBaseSettingFunc @@ -40,6 +41,7 @@ func newTrunkBaseSettingProxy(clientConfig *platformclientv2.Configuration) *tru return &trunkbaseSettingProxy{ clientConfig: clientConfig, edgesApi: edgesApi, + customApiClient: customapi.NewClient(clientConfig, ResourceType), getTrunkBaseSettingByIdAttr: getTrunkBaseSettingByIdFn, createTrunkBaseSettingAttr: createTrunkBaseSettingFn, updateTrunkBaseSettingAttr: updateTrunkBaseSettingFn, @@ -136,52 +138,15 @@ func getAllTrunkBaseSettingsFn(ctx context.Context, p *trunkbaseSettingProxy, na // The SDK function is too cumbersome because of the various boolean query parameters. // This function was written in order to leave them out and make a single API call func getTelephonyProvidersEdgesTrunkbasesettings(p *trunkbaseSettingProxy, pageNumber int, pageSize int, name string) (*platformclientv2.Trunkbaseentitylisting, *platformclientv2.APIResponse, error) { - headerParams := make(map[string]string) - if p.clientConfig.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + p.clientConfig.AccessToken - } - // add default headers if any - for key := range p.clientConfig.DefaultHeader { - headerParams[key] = p.clientConfig.DefaultHeader[key] + qp := map[string]string{ + "pageNumber": fmt.Sprintf("%d", pageNumber), + "pageSize": fmt.Sprintf("%d", pageSize), } - - queryParams := make(map[string]string) - queryParams["pageNumber"] = p.clientConfig.APIClient.ParameterToString(pageNumber, "") - queryParams["pageSize"] = p.clientConfig.APIClient.ParameterToString(pageSize, "") if name != "" { - queryParams["name"] = p.clientConfig.APIClient.ParameterToString(name, "") + qp["name"] = name } - - // to determine the Content-Type header - httpContentTypes := []string{"application/json"} - - // set Content-Type header - httpContentType := p.clientConfig.APIClient.SelectHeaderContentType(httpContentTypes) - if httpContentType != "" { - headerParams["Content-Type"] = httpContentType - } - - // set Accept header - httpHeaderAccept := p.clientConfig.APIClient.SelectHeaderAccept([]string{ - "application/json", - }) - if httpHeaderAccept != "" { - headerParams["Accept"] = httpHeaderAccept - } - var successPayload *platformclientv2.Trunkbaseentitylisting - path := p.clientConfig.BasePath + "/api/v2/telephony/providers/edges/trunkbasesettings" - response, err := p.clientConfig.APIClient.CallAPI(path, http.MethodGet, nil, headerParams, queryParams, nil, "", nil, "") - if err != nil { - return nil, nil, err - } - - if response.Error != nil { - err = errors.New(response.ErrorMessage) - } else { - err = json.Unmarshal(response.RawBody, &successPayload) - } - - return successPayload, response, err + queryParams := customapi.NewQueryParams(qp) + return customapi.Do[platformclientv2.Trunkbaseentitylisting](context.Background(), p.customApiClient, customapi.MethodGet, "/api/v2/telephony/providers/edges/trunkbasesettings", nil, queryParams) } func createTrunkBaseSettingFn(ctx context.Context, p *trunkbaseSettingProxy, trunkBaseSetting platformclientv2.Trunkbase) (*platformclientv2.Trunkbase, *platformclientv2.APIResponse, error) { diff --git a/genesyscloud/user/resource_genesyscloud_user_utils.go b/genesyscloud/user/resource_genesyscloud_user_utils.go index 53d232e1c..53e455575 100644 --- a/genesyscloud/user/resource_genesyscloud_user_utils.go +++ b/genesyscloud/user/resource_genesyscloud_user_utils.go @@ -12,6 +12,7 @@ import ( "strings" "time" + customapi "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/custom_api_client" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util" chunksProcess "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/chunks" "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/util/lists" @@ -311,16 +312,13 @@ func updateUserRoutingUtilization(ctx context.Context, d *schema.ResourceData, p labelUtilizations := allSettings["label_utilizations"].([]interface{}) if len(labelUtilizations) > 0 { - // Set resource context for SDK debug logging - - apiClient := &proxy.routingApi.Configuration.APIClient - - path := fmt.Sprintf("%s/api/v2/routing/users/%s/utilization", proxy.routingApi.Configuration.BasePath, d.Id()) - headerParams := buildHeaderParams(proxy.routingApi) - requestPayload := make(map[string]interface{}) - requestPayload["utilization"] = buildMediaTypeUtilizations(allSettings) - requestPayload["labelUtilizations"] = buildLabelUtilizationsRequest(labelUtilizations) - _, err = apiClient.CallAPI(path, "PUT", requestPayload, headerParams, nil, nil, "", nil, "") + c := customapi.NewClient(proxy.routingApi.Configuration, ResourceType) + path := fmt.Sprintf("/api/v2/routing/users/%s/utilization", d.Id()) + requestPayload := map[string]interface{}{ + "utilization": buildMediaTypeUtilizations(allSettings), + "labelUtilizations": buildLabelUtilizationsRequest(labelUtilizations), + } + _, err = customapi.DoNoResponse(ctx, c, customapi.MethodPut, path, requestPayload, nil) } else { sdkSettings := make(map[string]platformclientv2.Mediautilization) for sdkType, schemaType := range getUtilizationMediaTypes() { @@ -547,11 +545,9 @@ func restoreDeletedUser(ctx context.Context, d *schema.ResourceData, meta interf func readUserRoutingUtilization(d *schema.ResourceData, proxy *userProxy) diag.Diagnostics { log.Printf("Getting user utilization") - apiClient := &proxy.routingApi.Configuration.APIClient - - path := fmt.Sprintf("%s/api/v2/routing/users/%s/utilization", proxy.routingApi.Configuration.BasePath, d.Id()) - headerParams := buildHeaderParams(proxy.routingApi) - response, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil, "") + c := customapi.NewClient(proxy.routingApi.Configuration, ResourceType) + path := fmt.Sprintf("/api/v2/routing/users/%s/utilization", d.Id()) + rawBody, response, err := customapi.DoRaw(context.Background(), c, customapi.MethodGet, path, nil, nil) if err != nil { if util.IsStatus404(response) { @@ -562,7 +558,7 @@ func readUserRoutingUtilization(d *schema.ResourceData, proxy *userProxy) diag.D } agentUtilization := &agentUtilizationWithLabels{} - err = json.Unmarshal(response.RawBody, &agentUtilization) + err = json.Unmarshal(rawBody, &agentUtilization) if err != nil { log.Printf("[WARN] failed to unmarshal json: %s", err.Error()) } @@ -955,20 +951,6 @@ func getUtilizationMediaTypes() map[string]string { return utilizationMediaTypes } -func buildHeaderParams(routingAPI *platformclientv2.RoutingApi) map[string]string { - headerParams := make(map[string]string) - - for key := range routingAPI.Configuration.DefaultHeader { - headerParams[key] = routingAPI.Configuration.DefaultHeader[key] - } - - headerParams["Authorization"] = "Bearer " + routingAPI.Configuration.AccessToken - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - return headerParams -} - func flattenUtilizationSetting(settings mediaUtilization) []interface{} { settingsMap := make(map[string]interface{})