Repository: getkin/kin-openapi
Pull request #321 (2021-03-05 23:59:24)

Adds oneOf/discriminator/mapping management
Ciao, the change managed to make me validate/parse this spec
```
openapi: 3.0.0
info:
  version: 0.2.0
  title: yaAPI

paths:

  /config:
    put:
      operationId: ConfigSet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/config'
      responses:
        '200':
          description: Ok

components:
  schemas:
    config:
      oneOf:
        - $ref: '#/components/schemas/simplified'
        - $ref: '#/components/schemas/full'
      discriminator:
        propertyName: conf_type
        mapping:
          full: '#/components/schemas/full'
          simplified: '#/components/schemas/simplified'
    generic_config:
      type: object
      required:
        - conf_type
      properties:
        conf_type:
          type: string
          enum:
            - simplified
            - full
      discriminator:
        propertyName: conf_type
        mapping:
          full: '#/components/schemas/full'
          simplified: '#/components/schemas/simplified'
    full:
      allOf:
      - $ref: '#/components/schemas/generic_config'
      - type: object
        description: Full configuration
        properties:
          base64:
            type: string
      
    simplified:
      allOf:
      - $ref: '#/components/schemas/generic_config'
      - type: object
        description: Simplified (aided) minimal config
        properties:
          output:
            type: object
```
with this call

    curl -X PUT "http://127.0.0.1:8667/api/1/mgmt/config" -H  "accept: */*" -H  "Content-Type: application/json" -d "{\"conf_type\":\"full\",\"base64\":\"stuff\"}"

Before the change the validation failed cause the code hit "ok" two times here

https://github.com/getkin/kin-openapi/blob/master/openapi3/schema.go#L850

I'm positive this is breaking other cases but maybe you know how to rework it. Besides..maybe this has to do with

https://github.com/getkin/kin-openapi/issues/80

Let me know if it's rubbish or if it could be used with some polishing

fenollp: Hi! Thanks for taking time to do this.
Could you add your example code as a test? I can add #80's code as test as well later if you can't do it.
Some notes:
* do `okcheck` instead of `okcheck == true`
* casting to string needs a check as it panics if it isn't a string

In what way do you expect this to break other cases?
RiccardoManfrin: > Hi! Thanks for taking time to do this.
> Could you add your example code as a test? 
will try
> I can add #80's code as test as well later if you can't do it.
> Some notes:
> 
>     * do `okcheck` instead of `okcheck == true`
will do.. really I didn't mean to make it prod grade
> 
>     * casting to string needs a check as it panics if it isn't a string
will do
> 
> In what way do you expect this to break other cases?
I've no idea but this seems to be so needed (at least by me) that I thought it could not be _so easy_ unless something else was breaking..

fenollp: This patch looks pretty similar: https://github.com/xeipuuv/gojsonschema/pull/297/files so I guess you're on point :)

Patch:
diff --git a/openapi3/schema.go b/openapi3/schema.go
index 4968fec..bed51c1 100644
--- a/openapi3/schema.go
+++ b/openapi3/schema.go
@@ -847,7 +847,19 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val
 			err := v.visitJSON(settings, value)
 			settings.failfast = oldfailfast
 			if err == nil {
-				ok++
+				if schema.Discriminator != nil {
+					pn := schema.Discriminator.PropertyName
+					if valuemap, okcheck := value.(map[string]interface{}); okcheck {
+						if discriminatorVal, okcheck := valuemap[pn]; okcheck == true {
+							mapref, okcheck := schema.Discriminator.Mapping[discriminatorVal.(string)]
+							if okcheck && mapref == item.Ref {
+								ok++
+							}
+						}
+					}
+				} else {
+					ok++
+				}
 			}
 		}
 		if ok != 1 {


Test patch:
diff --git a/openapi3filter/validation_discriminator_test.go b/openapi3filter/validation_discriminator_test.go
new file mode 100644
index 0000000..51d804f
--- /dev/null
+++ b/openapi3filter/validation_discriminator_test.go
@@ -0,0 +1,98 @@
+package openapi3filter
+
+import (
+	"bytes"
+	"context"
+	"net/http"
+	"testing"
+
+	"github.com/getkin/kin-openapi/openapi3"
+	"github.com/stretchr/testify/require"
+)
+
+var yaJsonSpecWithDiscriminator = []byte(`
+openapi: 3.0.0
+info:
+  version: 0.2.0
+  title: yaAPI
+
+paths:
+
+  /blob:
+    put:
+      operationId: SetObj
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/blob'
+      responses:
+        '200':
+          description: Ok
+
+components:
+  schemas:
+    blob:
+      oneOf:
+        - $ref: '#/components/schemas/objA'
+        - $ref: '#/components/schemas/objB'
+      discriminator:
+        propertyName: discr
+        mapping:
+          objA: '#/components/schemas/objA'
+          objB: '#/components/schemas/objB'
+    genericObj:
+      type: object
+      required:
+        - discr
+      properties:
+        discr:
+          type: string
+          enum:
+            - objA
+            - objB
+      discriminator:
+        propertyName: discr
+        mapping:
+          objA: '#/components/schemas/objA'
+          objB: '#/components/schemas/objB'
+    objA:
+      allOf:
+      - $ref: '#/components/schemas/genericObj'
+      - type: object
+        properties:
+          base64:
+            type: string
+      
+    objB:
+      allOf:
+      - $ref: '#/components/schemas/genericObj'
+      - type: object
+        properties:
+          value:
+            type: integer
+`)
+
+func forgeRequest(body string) *http.Request {
+	iobody := bytes.NewReader([]byte(body))
+	req, _ := http.NewRequest("PUT", "/blob", iobody)
+	req.Header.Add("Content-Type", "application/json")
+	return req
+}
+
+func TestValidationWithDiscriminatorSelection(t *testing.T) {
+	openapi, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(yaJsonSpecWithDiscriminator)
+	require.NoError(t, err)
+	router := NewRouter().WithSwagger(openapi)
+	req := forgeRequest(`{"discr": "objA", "base64": "S25vY2sgS25vY2ssIE5lbyAuLi4="}`)
+	route, pathParams, _ := router.FindRoute(req.Method, req.URL)
+	requestValidationInput := &RequestValidationInput{
+		Request:    req,
+		PathParams: pathParams,
+		Route:      route,
+	}
+	ctx := context.Background()
+	err = ValidateRequest(ctx, requestValidationInput)
+	require.NoError(t, err)
+}