Skip to content

Commit

Permalink
fix: urlencode deepObject for json scalar
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac committed Dec 13, 2024
1 parent 2ec5ce8 commit d14975c
Show file tree
Hide file tree
Showing 5 changed files with 581 additions and 8 deletions.
13 changes: 7 additions & 6 deletions connector/internal/contenttype/url_encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func (c *URLParameterEncoder) EncodeParameterValues(objectField *rest.ObjectFiel
if !ok {
return nil, fmt.Errorf("%s: failed to evaluate object, got <%s> %v", strings.Join(fieldPaths, ""), kind, anyValue)
}

for key, fieldInfo := range objectInfo.Fields {
fieldVal := object[key]
output, err := c.EncodeParameterValues(&fieldInfo, reflect.ValueOf(fieldVal), append(fieldPaths, "."+key))
Expand Down Expand Up @@ -248,12 +249,6 @@ func encodeParameterReflectionValues(reflectValue reflect.Value, fieldPaths []st
}

kind := reflectValue.Kind()
if result, err := StringifySimpleScalar(reflectValue, kind); err == nil {
return []ParameterItem{
NewParameterItem([]Key{}, []string{result}),
}, nil
}

switch kind {
case reflect.Slice, reflect.Array:
return encodeParameterReflectionSlice(reflectValue, fieldPaths)
Expand All @@ -262,6 +257,12 @@ func encodeParameterReflectionValues(reflectValue reflect.Value, fieldPaths []st
case reflect.Struct:
return encodeParameterReflectionStruct(reflectValue, fieldPaths)
default:
if result, err := StringifySimpleScalar(reflectValue, kind); err == nil {
return []ParameterItem{
NewParameterItem([]Key{}, []string{result}),
}, nil
}

return nil, fmt.Errorf("%s: failed to encode parameter, got %s", strings.Join(fieldPaths, ""), kind)
}
}
Expand Down
77 changes: 76 additions & 1 deletion connector/internal/request_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"encoding/json"
"io"
"net/url"
"os"
"testing"
Expand All @@ -10,7 +11,7 @@ import (
"gotest.tools/v3/assert"
)

func TestEvalURLAndHeaderParameters(t *testing.T) {
func TestQueryEvalURLAndHeaderParameters(t *testing.T) {
testCases := []struct {
name string
rawArguments string
Expand Down Expand Up @@ -77,6 +78,80 @@ func TestEvalURLAndHeaderParameters(t *testing.T) {
}
}

func TestMutationEvalURLAndHeaderParameters(t *testing.T) {
testCases := []struct {
name string
rawArguments string
expectedURL string
expectedBody string
errorMsg string
headers map[string]string
}{
{
name: "PostBillingMeterEvents",
rawArguments: `{
"body": {
"event_name": "k8hAOi2B52",
"identifier": "identifier_123",
"payload": {
"value": "25",
"stripe_customer_id": "cus_NciAYcXfLnqBoz"
},
"timestamp": 931468280
}
}`,
expectedURL: "/v1/billing/meter_events",
expectedBody: "event_name=k8hAOi2B52&identifier=identifier_123&payload[value]=25&payload[stripe_customer_id]=cus_NciAYcXfLnqBoz&timestamp=931468280",
},
}

ndcSchema := createMockSchema(t)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var info *rest.OperationInfo
for key, f := range ndcSchema.Procedures {
if key == tc.name {
info = &f
break
}
}
var arguments map[string]any
assert.NilError(t, json.Unmarshal([]byte(tc.rawArguments), &arguments))

builder := RequestBuilder{
Schema: ndcSchema,
Operation: info,
Arguments: arguments,
}

result, err := builder.Build()
if tc.errorMsg != "" {
assert.ErrorContains(t, err, tc.errorMsg)

return
}

assert.NilError(t, err)
decodedValue, err := url.QueryUnescape(result.URL.String())
assert.NilError(t, err)
assert.Equal(t, tc.expectedURL, decodedValue)

for k, v := range tc.headers {
assert.Equal(t, v, result.Headers.Get(k))
}

bodyBytes, err := io.ReadAll(result.Body)
assert.NilError(t, err)
expected, err := url.ParseQuery(tc.expectedBody)
assert.NilError(t, err)
body, err := url.ParseQuery(string(bodyBytes))
assert.NilError(t, err)

assert.DeepEqual(t, expected, body)
})
}
}

func TestEvalURLAndHeaderParametersOAS2(t *testing.T) {
testCases := []struct {
name string
Expand Down
229 changes: 228 additions & 1 deletion ndc-http-schema/openapi/testdata/petstore3/expected.json
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,99 @@
"name": "##default"
}
},
"BillingMeterEvent": {
"description": "Meter events represent actions that customers take in your system. You can use meter events to bill a customer based on their usage. Meter events are associated with billing meters, which define both the contents of the events payload and how to aggregate those events.",
"fields": {
"created": {
"description": "Time at which the object was created. Measured in seconds since the Unix epoch.",
"type": {
"name": "UnixTime",
"type": "named"
},
"http": {
"type": [
"integer"
],
"format": "unix-time"
}
},
"event_name": {
"description": "The name of the meter event. Corresponds with the `event_name` field on a meter.",
"type": {
"name": "String",
"type": "named"
},
"http": {
"type": [
"string"
],
"maxLength": 100
}
},
"identifier": {
"description": "A unique identifier for the event.",
"type": {
"name": "String",
"type": "named"
},
"http": {
"type": [
"string"
],
"maxLength": 5000
}
},
"livemode": {
"description": "Has the value `true` if the object exists in live mode or the value `false` if the object exists in test mode.",
"type": {
"name": "Boolean",
"type": "named"
},
"http": {
"type": [
"boolean"
]
}
},
"object": {
"description": "String representing the object's type. Objects of the same type share the same value.",
"type": {
"name": "BillingMeterEventObject",
"type": "named"
},
"http": {
"type": [
"string"
]
}
},
"payload": {
"description": "The payload of the event. This contains the fields corresponding to a meter's `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and `value_settings.event_payload_key` (default is `value`). Read more about the [payload](https://stripe.com/docs/billing/subscriptions/usage-based/recording-usage#payload-key-overrides).",
"type": {
"name": "JSON",
"type": "named"
},
"http": {
"type": [
"object"
]
}
},
"timestamp": {
"description": "The timestamp passed in when creating the event. Measured in seconds since the Unix epoch.",
"type": {
"name": "UnixTime",
"type": "named"
},
"http": {
"type": [
"integer"
],
"format": "unix-time"
}
}
}
},
"Book": {
"fields": {
"attr": {
Expand Down Expand Up @@ -1457,6 +1550,91 @@
"name": "pet"
}
},
"PostBillingMeterEventsBody": {
"fields": {
"event_name": {
"description": "The name of the meter event. Corresponds with the `event_name` field on a meter.",
"type": {
"name": "String",
"type": "named"
},
"http": {
"type": [
"string"
],
"maxLength": 100
}
},
"expand": {
"description": "Specifies which fields in the response should be expanded.",
"type": {
"type": "nullable",
"underlying_type": {
"element_type": {
"name": "String",
"type": "named"
},
"type": "array"
}
},
"http": {
"type": [
"array"
],
"items": {
"type": [
"string"
],
"maxLength": 5000
}
}
},
"identifier": {
"description": "A unique identifier for the event. If not provided, one is generated. We recommend using UUID-like identifiers. We will enforce uniqueness within a rolling period of at least 24 hours. The enforcement of uniqueness primarily addresses issues arising from accidental retries or other problems occurring within extremely brief time intervals. This approach helps prevent duplicate entries and ensures data integrity in high-frequency operations.",
"type": {
"type": "nullable",
"underlying_type": {
"name": "String",
"type": "named"
}
},
"http": {
"type": [
"string"
],
"maxLength": 100
}
},
"payload": {
"description": "The payload of the event. This must contain the fields corresponding to a meter's `customer_mapping.event_payload_key` (default is `stripe_customer_id`) and `value_settings.event_payload_key` (default is `value`). Read more about the [payload](https://docs.stripe.com/billing/subscriptions/usage-based/recording-usage#payload-key-overrides).",
"type": {
"name": "JSON",
"type": "named"
},
"http": {
"type": [
"object"
]
}
},
"timestamp": {
"description": "The time of the event. Measured in seconds since the Unix epoch. Must be within the past 35 calendar days or up to 5 minutes in the future. Defaults to current timestamp if not specified.",
"type": {
"type": "nullable",
"underlying_type": {
"name": "UnixTime",
"type": "named"
}
},
"http": {
"type": [
"integer"
],
"format": "unix-time"
}
}
}
},
"PostCheckoutSessionsBody": {
"fields": {
"after_expiration": {
Expand Down Expand Up @@ -7232,6 +7410,45 @@
"type": "named"
}
},
"PostBillingMeterEvents": {
"request": {
"url": "/v1/billing/meter_events",
"method": "post",
"requestBody": {
"contentType": "application/x-www-form-urlencoded",
"encoding": {
"expand": {
"style": "deepObject",
"explode": true
},
"payload": {
"style": "deepObject",
"explode": true
}
}
},
"response": {
"contentType": "application/json"
}
},
"arguments": {
"body": {
"description": "Request body of POST /v1/billing/meter_events",
"type": {
"name": "PostBillingMeterEventsBody",
"type": "named"
},
"http": {
"in": "body"
}
}
},
"description": "Create a billing meter event",
"result_type": {
"name": "BillingMeterEvent",
"type": "named"
}
},
"PostCheckoutSessions": {
"request": {
"url": "/v1/checkout/sessions",
Expand Down Expand Up @@ -7353,7 +7570,7 @@
"servers": [
{
"url": {
"value": "https://files.stripe.com/",
"value": "https://files.stripe.com",
"env": "PET_STORE_SERVER_URL"
}
}
Expand Down Expand Up @@ -8131,6 +8348,16 @@
}
},
"scalar_types": {
"BillingMeterEventObject": {
"aggregate_functions": {},
"comparison_operators": {},
"representation": {
"one_of": [
"billing.meter_event"
],
"type": "enum"
}
},
"Binary": {
"aggregate_functions": {},
"comparison_operators": {},
Expand Down
Loading

0 comments on commit d14975c

Please sign in to comment.