Skip to content

Commit

Permalink
Support primitives in an anyOf from the parameters coercer
Browse files Browse the repository at this point in the history
I was running `stripe-ruby`'s test suite against a recent version of the
OpenAPI, and noticed that unfortunately, recent updates have broken it.
In particular, the new tiers feature under plan creation now has a
primitive type nested in an `anyOf`, which is something that we've never
had to handle before:

``` yaml
up_to:
  anyOf:
  - enum:
    - inf
    type: string
  - type: integer
```

`stripe-ruby` tries to run this against it:

``` ruby
plan = Stripe::Plan.create(
  ...
  tiers: [{ up_to: 100, amount: 1000 }, { up_to: "inf", amount: 2000 }]
)
```

What's supposed to happen is that "100" is coerced to an integer, but it's not
because the integer possibility is nested in an `anyOf` as seen above.

This patch addresses the problem by allowing the coercer to recurse into an
`anyOf` to try coercion. It's able to theoretically handle any number of
`anyOf` possibilities, and will dutifully try coercing each one until it finds
a coercable type (or it doesn't, which is okay).

I've run `stripe-ruby`'s test suite against a version of `stripe-mock` with
this patch in it to verify that it works.
  • Loading branch information
brandur committed Apr 26, 2018
1 parent 4362e10 commit 50ceb42
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 2 deletions.
47 changes: 45 additions & 2 deletions param/coercer/coercer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func CoerceParams(schema *spec.Schema, data map[string]interface{}) error {
CoerceParams(subSchema.Items, itemValMap)
} else if subSchema.Items.Type != "" {
// Handles the case of an array of primitive types
itemValCoerced, ok := coercePrimitiveType(itemVal, subSchema.Items.Type)
itemValCoerced, ok := coerceSchema(itemVal, subSchema.Items)
if ok {
valArr[i] = itemValCoerced
}
Expand All @@ -61,7 +61,7 @@ func CoerceParams(schema *spec.Schema, data map[string]interface{}) error {
continue
}

valCoerced, ok := coercePrimitiveType(val, subSchema.Type)
valCoerced, ok := coerceSchema(val, subSchema)
if ok {
data[key] = valCoerced
}
Expand Down Expand Up @@ -127,6 +127,49 @@ func coercePrimitiveType(val interface{}, primitiveType string) (interface{}, bo
return nil, false
}

// coerceSchema tries to coerce a schema containing a primitive type from the
// given generic interface{} value.
//
// It's similar to coercePrimitiveType above (and indeed calls into it), but
// also handles the case of an anyOf schema that supports a number of different
// primitve types.
func coerceSchema(val interface{}, schema *spec.Schema) (interface{}, bool) {
if isSchemaPrimitiveType(schema) {
return coercePrimitiveType(val, schema.Type)
} else if schema.AnyOf != nil {
for _, subSchema := range schema.AnyOf {
val, ok := coerceSchema(val, subSchema)
if ok {
return val, ok
}
}
}

return nil, false
}

// isSchemaPrimitiveType checks whether the given schema is a coercable
// primitive type (as opposed to an object or array).
//
// The conditional ladder in this function should be *identical* to the one in
// coercePrimitiveType (i.e., if support is added for a new type, it needs to
// be added in both places).
func isSchemaPrimitiveType(schema *spec.Schema) bool {
if schema.Type == booleanType {
return true
}

if schema.Type == integerType {
return true
}

if schema.Type == numberType {
return true
}

return false
}

// parseIntegerIndexedMap tries to parse a map that has all integer-indexed
// keys (e.g. { "0": ..., "1": "...", "2": "..." }) as a slice. We only try to
// do this when we know that the target schema requires an array.
Expand Down
18 changes: 18 additions & 0 deletions param/coercer/coercer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ import (
"github.com/stripe/stripe-mock/spec"
)

func TestCoerceParams_AnyOfCoercion(t *testing.T) {
schema := &spec.Schema{Properties: map[string]*spec.Schema{
"objectorintkey": {
AnyOf: []*spec.Schema{
{Type: objectType},
{Type: integerType},
},
},
}}
data := map[string]interface{}{
"objectorintkey": "123",
}

err := CoerceParams(schema, data)
assert.NoError(t, err)
assert.Equal(t, 123, data["objectorintkey"])
}

func TestCoerceParams_ArrayCoercion(t *testing.T) {
// Array of primitive values
{
Expand Down

0 comments on commit 50ceb42

Please sign in to comment.