twilio / twilio-oai-generator

Twilio OpenAPI client generator
MIT License
26 stars 16 forks source link

Panic when attempting to update a Flex instance configuration due to an incorrect media type in the request #49

Open RJPearson94 opened 3 years ago

RJPearson94 commented 3 years ago

Issue Summary

Hi, I have noticed that you cannot currently update a Twilio Flex instance configuration using the Twilio Go SDK. Currently on every POST request the Content-Type header is hardcoded to be application/x-www-form-urlencoded as seen here This is suitable for the majority of POST requests to the various Twilio API however to update the Flex Configuration, JSON needs to be sent in the request body.

To fix this I believe it will require changes in the Go SDK with the request handler and client being modified. Changes will also be required inside of the API mustache template in this repo.

I was thinking that inside the request handler in the Go SDK, the current Post function would be updated to create a new map that combines the Content-Type header (with the value of application/x-www-form-urlencoded) with the header map which is passed into the function. I was going with this approach to prevent mutating the headers map but if you want the header can either be passed in as another argument to SendRequest or the value could be appended to the headers map. The current logic to set the Content-Type header inside of send request would be deleted. Another function PostJson would be added which would accept the path, body (of type map[string]interface{}) and the headers. This function would set the value of the Content-Type header to application/json.

In the generator, the API mustache template file will be updated to check if the request media type is application/json then the initialization of the data map (url.Values) and population of the params data into the map will be replaced with the params struct being marshalled into a map of type map[string]interface{} using the build-in Marshal function in the json package. Instead of the Post function being called on the request handler the PostJson function will be called. If the request media type is not supplied or is application/x-www-form-urlencoded then the current implementation will be used, as per the current implementation.

The JSON and YML Open API templates don't currently include the request body schema which would be used to generate the input/ params struct, so the changes outlined above will resolve the issue highlighted however the Flex configuration cannot be updated using the SDK until the definition has been updated. As this is an issue with the Open API templates do you want the issue for this raised on the twilio-oai repo?

This implementation should be flexible enough to be replicated to add support for creating a Serverless Asset Version and creating a Serverless Function Version via the SDK, both of which require form data to be supplied.

If you are happy with the changes I am happy to raise a PR for this.

Steps to Reproduce

  1. Copy the code below
  2. Execute the code using go run .go

Code Snippet

package main

import (
  "log"
  "os"

  "github.com/twilio/twilio-go"
)

func main() {
  accountSid := os.Getenv("TWILIO_ACCOUNT_SID")
  authToken := os.Getenv("TWILIO_AUTH_TOKEN")
  client := twilio.NewRestClient(accountSid, authToken)

  resp, err := client.FlexV1.UpdateConfiguration()

  if err != nil {
    log.Panic(err.Error())
  }

  log.Printf("SID: %s", StringValue(resp.FlexServiceInstanceSid))
}

// StringValue is a utility method for obtaining the string value of a pointer
func StringValue(value *string) string {
  return *value
}

Exception/Log

2021/05/26 00:06:04 Status: 415 - ApiError 20415: The server does not support the media type transmitted in the request. (null) More info: https://www.twilio.com/docs/errors/20415
panic: Status: 415 - ApiError 20415: The server does not support the media type transmitted in the request. (null) More info: https://www.twilio.com/docs/errors/20415

goroutine 1 [running]:
log.Panic(0xc000151f68, 0x1, 0x1)
        /usr/local/opt/go/libexec/src/log/log.go:351 +0xae
main.main()
        /Users/RJPearson94/Documents/Code/Twilio/go-sdk/flex/configuration/flex_configuration.go:18 +0x18b
exit status 2

Technical details:

eshanholtz commented 3 years ago

@RJPearson94

Thank you for the in depth issue and details! A corresponding issue in twilio-oai would be appreciated to outline the missing request body schema. We are aware our library doesn't currently support JSON ingress but it's definitely on the horizon! We'd greatly appreciate any PR you're willing to submit (even as a proof-of-concept) to support our effort. If you're submitting PRs to multiple repos (i.e. one for updating mustache templates here, plus any corresponding changes in twilio-go) please be sure they include links to each other in the description. This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog.

RJPearson94 commented 3 years ago

Thanks @eshanholtz, I'll take a look at putting together a few PRs. I'll get the issue raised on the twilio-oai repo too

RJPearson94 commented 3 years ago

Hi @eshanholtz,

Apologies for the delay in raising these PRs.

I have raised 2 PR's (twilio/twilio-go#83 and twilio/twilio-go#84) against the Go SDK repo and 1 PR against this repo (#54) to lay the groundwork for resolving this issue. Unfortunately, until twilio/twilio-oai#36 is resolved, Flex Configuration cannot be updated via the Go SDK.

I have tested this locally by replacing the operations for the /v1/Configuration path in the Flex OpenAPI spec YAML file with the YAML below

/v1/Configuration:
  description: Configuration for a Flex instance
  get:
   description: ''
   operationId: FetchConfiguration
   parameters:
   - description: The Pinned UI version of the Configuration resource to fetch.
    in: query
    name: UiVersion
    schema:
     type: string
   responses:
    '200':
     content:
      application/json:
       schema:
        $ref: '#/components/schemas/flex.v1.configuration'
     description: OK
   security:
   - accountSid_authToken: []
   x-maturity:
   - GA
  post:
   description: ''
   operationId: UpdateConfiguration
   requestBody:
    content:
     application/json:
      schema:
       properties:
        account_sid:
         description: The SID of the Account that created the resource
         maxLength: 34
         minLength: 34
         nullable: false
         pattern: ^AC[0-9a-fA-F]{32}$
         type: string
        attributes:
         description: An object that contains application-specific data
         nullable: true
         type: object
        call_recording_enabled:
         description: Whether call recording is enabled
         nullable: true
         type: boolean
        call_recording_webhook_url:
         description: The call recording webhook URL
         format: uri
         nullable: true
         type: string
        chat_service_instance_sid:
         description: The SID of the chat service this user belongs to
         maxLength: 34
         minLength: 34
         nullable: true
         pattern: ^IS[0-9a-fA-F]{32}$
         type: string
        crm_attributes:
         description: An object that contains the CRM attributes
         nullable: true
         type: object
        crm_callback_url:
         description: The CRM Callback URL
         format: uri
         nullable: true
         type: string
        crm_enabled:
         description: Whether CRM is present for Flex
         nullable: true
         type: boolean
        crm_fallback_url:
         description: The CRM Fallback URL
         format: uri
         nullable: true
         type: string
        crm_type:
         description: The CRM Type
         nullable: true
         type: string
        date_created:
         description: The ISO 8601 date and time in GMT when the Configuration resource
          was created
         format: date-time
         nullable: true
         type: string
        date_updated:
         description: The ISO 8601 date and time in GMT when the Configuration resource
          was last updated
         format: date-time
         nullable: true
         type: string
        integrations:
         description: A list of objects that contain the configurations for the Integrations
          supported in this configuration
         items:
          type: object
         nullable: true
         type: array
        markdown:
         description: Configurable parameters for Markdown
         nullable: true
         type: object
        messaging_service_instance_sid:
         description: The SID of the Messaging service instance
         maxLength: 34
         minLength: 34
         nullable: true
         pattern: ^MG[0-9a-fA-F]{32}$
         type: string
        notifications:
         description: Configurable parameters for Notifications
         nullable: true
         type: object
        outbound_call_flows:
         description: The list of outbound call flows
         nullable: true
         type: object
        plugin_service_attributes:
         description: The plugin service attributes
         nullable: true
         type: object
        plugin_service_enabled:
         description: Whether the plugin service enabled
         nullable: true
         type: boolean
        public_attributes:
         description: The list of public attributes
         nullable: true
         type: object
        queue_stats_configuration:
         description: Configurable parameters for Queues Statistics
         nullable: true
         type: object
        runtime_domain:
         description: The URL where the Flex instance is hosted
         format: uri
         nullable: true
         type: string
        serverless_service_sids:
         description: The list of serverless service SIDs
         items:
          maxLength: 34
          minLength: 34
          pattern: ^ZS[0-9a-fA-F]{32}$
          type: string
         nullable: true
         type: array
        taskrouter_skills:
         description: The Skill description for TaskRouter workers
         items:
          type: object
         nullable: true
         type: array
        taskrouter_target_taskqueue_sid:
         description: The SID of the TaskRouter Target TaskQueue
         maxLength: 34
         minLength: 34
         nullable: true
         pattern: ^WQ[0-9a-fA-F]{32}$
         type: string
        taskrouter_target_workflow_sid:
         description: The SID of the TaskRouter target Workflow
         maxLength: 34
         minLength: 34
         nullable: true
         pattern: ^WW[0-9a-fA-F]{32}$
         type: string
        taskrouter_taskqueues:
         description: The list of TaskRouter TaskQueues
         items:
          type: object
         nullable: true
         type: array
        taskrouter_worker_attributes:
         description: The TaskRouter Worker attributes
         nullable: true
         type: object
        taskrouter_worker_channels:
         description: The TaskRouter default channel capacities and availability
          for workers
         nullable: true
         type: object
        ui_attributes:
         description: The object that describes Flex UI characteristics and settings
         nullable: true
         type: object
        ui_dependencies:
         description: The object that defines the NPM packages and versions to be
          used in Hosted Flex
         nullable: true
         type: object
        ui_language:
         description: The primary language of the Flex UI
         nullable: true
         type: string
        ui_version:
         description: The Pinned UI version
         nullable: true
         type: string
       title: UpdateFlexConfigurationRequest
       type: object
   responses:
    '200':
     content:
      application/json:
       schema:
        $ref: '#/components/schemas/flex.v1.configuration'
     description: OK
   security:
   - accountSid_authToken: []
   x-maturity:
   - GA
  servers:
  - url: https://flex-api.twilio.com
  x-twilio:
   defaultOutputProperties:
   - status
   - ui_language
   - ui_version
   - service_version
   pathType: instance

With this updated spec, the following code was generated for the Go SDK

// rest/flex/v1/api_defaults.go
...
// Optional parameters for the method 'UpdateConfiguration'
type UpdateConfigurationParams struct {
 //
 UpdateFlexConfigurationRequest *UpdateFlexConfigurationRequest `json:"UpdateFlexConfigurationRequest,omitempty"`
}

func (params *UpdateConfigurationParams) SetUpdateFlexConfigurationRequest(UpdateFlexConfigurationRequest UpdateFlexConfigurationRequest) *UpdateConfigurationParams {
 params.UpdateFlexConfigurationRequest = &UpdateFlexConfigurationRequest
 return params
}

func (c *DefaultApiService) UpdateConfiguration(params *UpdateConfigurationParams) (*FlexV1Configuration, error) {
 path := "/v1/Configuration"

 var data UpdateFlexConfigurationRequest
 if params.UpdateFlexConfigurationRequest != nil {
  data = *params.UpdateFlexConfigurationRequest
 } else {
  data = UpdateFlexConfigurationRequest{}
 }
 headers := make(map[string]interface{})

 resp, err := c.requestHandler.PostJson(c.baseURL+path, data, headers)
 if err != nil {
  return nil, err
 }

 defer resp.Body.Close()

 ps := &FlexV1Configuration{}
 if err := json.NewDecoder(resp.Body).Decode(ps); err != nil {
  return nil, err
 }

 return ps, err
}
...
// rest/flex/v1/model_update_flex_configuration_request.go
/*
 * Twilio - Flex
 *
 * This is the public Twilio REST API.
 *
 * API version: 1.16.1
 * Contact: support@twilio.com
 */

// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

package openapi

import (
 "time"
)

// UpdateFlexConfigurationRequest struct for UpdateFlexConfigurationRequest
type UpdateFlexConfigurationRequest struct {
 // The SID of the Account that created the resource
 AccountSid *string `json:"account_sid,omitempty"`
 // An object that contains application-specific data
 Attributes *map[string]interface{} `json:"attributes,omitempty"`
 // Whether call recording is enabled
 CallRecordingEnabled *bool `json:"call_recording_enabled,omitempty"`
 // The call recording webhook URL
 CallRecordingWebhookUrl *string `json:"call_recording_webhook_url,omitempty"`
 // The SID of the chat service this user belongs to
 ChatServiceInstanceSid *string `json:"chat_service_instance_sid,omitempty"`
 // An object that contains the CRM attributes
 CrmAttributes *map[string]interface{} `json:"crm_attributes,omitempty"`
 // The CRM Callback URL
 CrmCallbackUrl *string `json:"crm_callback_url,omitempty"`
 // Whether CRM is present for Flex
 CrmEnabled *bool `json:"crm_enabled,omitempty"`
 // The CRM Fallback URL
 CrmFallbackUrl *string `json:"crm_fallback_url,omitempty"`
 // The CRM Type
 CrmType *string `json:"crm_type,omitempty"`
 // The ISO 8601 date and time in GMT when the Configuration resource was created
 DateCreated *time.Time `json:"date_created,omitempty"`
 // The ISO 8601 date and time in GMT when the Configuration resource was last updated
 DateUpdated *time.Time `json:"date_updated,omitempty"`
 // A list of objects that contain the configurations for the Integrations supported in this configuration
 Integrations *[]map[string]interface{} `json:"integrations,omitempty"`
 // Configurable parameters for Markdown
 Markdown *map[string]interface{} `json:"markdown,omitempty"`
 // The SID of the Messaging service instance
 MessagingServiceInstanceSid *string `json:"messaging_service_instance_sid,omitempty"`
 // Configurable parameters for Notifications
 Notifications *map[string]interface{} `json:"notifications,omitempty"`
 // The list of outbound call flows
 OutboundCallFlows *map[string]interface{} `json:"outbound_call_flows,omitempty"`
 // The plugin service attributes
 PluginServiceAttributes *map[string]interface{} `json:"plugin_service_attributes,omitempty"`
 // Whether the plugin service enabled
 PluginServiceEnabled *bool `json:"plugin_service_enabled,omitempty"`
 // The list of public attributes
 PublicAttributes *map[string]interface{} `json:"public_attributes,omitempty"`
 // Configurable parameters for Queues Statistics
 QueueStatsConfiguration *map[string]interface{} `json:"queue_stats_configuration,omitempty"`
 // The URL where the Flex instance is hosted
 RuntimeDomain *string `json:"runtime_domain,omitempty"`
 // The list of serverless service SIDs
 ServerlessServiceSids *[]string `json:"serverless_service_sids,omitempty"`
 // The Skill description for TaskRouter workers
 TaskrouterSkills *[]map[string]interface{} `json:"taskrouter_skills,omitempty"`
 // The SID of the TaskRouter Target TaskQueue
 TaskrouterTargetTaskqueueSid *string `json:"taskrouter_target_taskqueue_sid,omitempty"`
 // The SID of the TaskRouter target Workflow
 TaskrouterTargetWorkflowSid *string `json:"taskrouter_target_workflow_sid,omitempty"`
 // The list of TaskRouter TaskQueues
 TaskrouterTaskqueues *[]map[string]interface{} `json:"taskrouter_taskqueues,omitempty"`
 // The TaskRouter Worker attributes
 TaskrouterWorkerAttributes *map[string]interface{} `json:"taskrouter_worker_attributes,omitempty"`
 // The TaskRouter default channel capacities and availability for workers
 TaskrouterWorkerChannels *map[string]interface{} `json:"taskrouter_worker_channels,omitempty"`
 // The object that describes Flex UI characteristics and settings
 UiAttributes *map[string]interface{} `json:"ui_attributes,omitempty"`
 // The object that defines the NPM packages and versions to be used in Hosted Flex
 UiDependencies *map[string]interface{} `json:"ui_dependencies,omitempty"`
 // The primary language of the Flex UI
 UiLanguage *string `json:"ui_language,omitempty"`
 // The Pinned UI version
 UiVersion *string `json:"ui_version,omitempty"`
}

I have tested the modified UpdateConfiguration function using the example code below and the code works as expected.

package main

import (
    "log"
    "os"

    "github.com/twilio/twilio-go"
    openapi "github.com/twilio/twilio-go/rest/flex/v1"
)

func main() {
    client := twilio.NewRestClient(os.Getenv("TWILIO_ACCOUNT_SID"), os.Getenv("TWILIO_AUTH_TOKEN"))

    resp, err := client.FlexV1.UpdateConfiguration(&openapi.UpdateConfigurationParams{
        UpdateFlexConfigurationRequest: &openapi.UpdateFlexConfigurationRequest{
            AccountSid: PtrString(os.Getenv("TWILIO_ACCOUNT_SID")),
        },
    })

    if err != nil {
        log.Panic(err.Error())
    }

    log.Printf("SID: %s", StringValue(resp.FlexServiceInstanceSid))
}

// PtrString is a utility method for obtaining a pointer for a value
func PtrString(value string) *string {
    return &value
}

// StringValue is a utility method for obtaining the string value of a pointer
func StringValue(value *string) string {
    return *value
}

Any feedback is greatly appreciated