Repository: getkin/kin-openapi
Pull request #511 (2022-03-16 13:35:55)

 openapi3: handle refs missing fragment
* fixes https://github.com/getkin/kin-openapi/issues/495
* superseeds https://github.com/getkin/kin-openapi/pull/867

Stricto sensu the issue is twofold
1. error message is misleading and hides the actual error
1. schema draft04 deserialization is incorrect in that a `#` ref under `definitions` being dereferenced leads to a "bad data" error, wrongly.

So far only (1) has been addressed.



Patch:
diff --git a/openapi3/loader.go b/openapi3/loader.go
index b7853ed..ffa17e8 100644
--- a/openapi3/loader.go
+++ b/openapi3/loader.go
@@ -310,7 +310,10 @@ func (loader *Loader) resolveComponent(doc *T, ref string, path *url.URL, resolv
 		return nil, nil, fmt.Errorf("cannot parse reference: %q: %v", ref, parsedURL)
 	}
 	fragment := parsedURL.Fragment
-	if !strings.HasPrefix(fragment, "/") {
+	if fragment == "" {
+		fragment = "/"
+	}
+	if fragment[0] != '/' {
 		return nil, nil, fmt.Errorf("expected fragment prefix '#/' in URI %q", ref)
 	}
 


Test patch:
diff --git a/openapi3/issue495_test.go b/openapi3/issue495_test.go
new file mode 100644
index 0000000..0ed52df
--- /dev/null
+++ b/openapi3/issue495_test.go
@@ -0,0 +1,157 @@
+package openapi3
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue495(t *testing.T) {
+	{
+		spec := []byte(`
+openapi: 3.0.1
+info:
+  version: v1
+  title: Products api
+components:
+  schemas:
+    someSchema:
+      type: object
+    schemaArray:
+      type: array
+      minItems: 1
+      items:
+        $ref: '#'
+paths:
+  /categories:
+    get:
+      responses:
+        '200':
+          description: ''
+          content:
+            application/json:
+              schema:
+                properties:
+                  allOf:
+                    $ref: '#/components/schemas/schemaArray'
+`[1:])
+
+		sl := NewLoader()
+
+		doc, err := sl.LoadFromData(spec)
+		require.NoError(t, err)
+
+		err = doc.Validate(sl.Context)
+		require.EqualError(t, err, `invalid components: schema "schemaArray": found unresolved ref: "#"`)
+	}
+
+	spec := []byte(`
+openapi: 3.0.1
+info:
+  version: v1
+  title: Products api
+components:
+  schemas:
+    someSchema:
+      type: object
+    schemaArray:
+      type: array
+      minItems: 1
+      items:
+        $ref: '#/components/schemas/someSchema'
+paths:
+  /categories:
+    get:
+      responses:
+        '200':
+          description: ''
+          content:
+            application/json:
+              schema:
+                properties:
+                  allOf:
+                    $ref: '#/components/schemas/schemaArray'
+`[1:])
+
+	sl := NewLoader()
+
+	doc, err := sl.LoadFromData(spec)
+	require.NoError(t, err)
+
+	err = doc.Validate(sl.Context)
+	require.NoError(t, err)
+
+	require.Equal(t, &Schema{Type: "object"}, doc.Components.Schemas["schemaArray"].Value.Items.Value)
+}
+
+func TestIssue495WithDraft04(t *testing.T) {
+	spec := []byte(`
+openapi: 3.0.1
+servers:
+- url: http://localhost:5000
+info:
+  version: v1
+  title: Products api
+  contact:
+    name: me
+    email: me@github.com
+  description: This is a sample
+paths:
+  /categories:
+    get:
+      summary: Provides the available categories for the store
+      operationId: list-categories
+      responses:
+        '200':
+          description: this is a desc
+          content:
+            application/json:
+              schema:
+                $ref: http://json-schema.org/draft-04/schema
+`[1:])
+
+	sl := NewLoader()
+	sl.IsExternalRefsAllowed = true
+
+	doc, err := sl.LoadFromData(spec)
+	require.NoError(t, err)
+
+	err = doc.Validate(sl.Context)
+	require.ErrorContains(t, err, `found unresolved ref: "#"`)
+}
+
+func TestIssue495WithDraft04Bis(t *testing.T) {
+	spec := []byte(`
+openapi: 3.0.1
+servers:
+- url: http://localhost:5000
+info:
+  version: v1
+  title: Products api
+  contact:
+    name: me
+    email: me@github.com
+  description: This is a sample
+paths:
+  /categories:
+    get:
+      summary: Provides the available categories for the store
+      operationId: list-categories
+      responses:
+        '200':
+          description: this is a desc
+          content:
+            application/json:
+              schema:
+                $ref: testdata/draft04.yml
+`[1:])
+
+	sl := NewLoader()
+	sl.IsExternalRefsAllowed = true
+
+	doc, err := sl.LoadFromData(spec)
+	require.NoError(t, err)
+
+	err = doc.Validate(sl.Context)
+	require.ErrorContains(t, err, `found unresolved ref: "#"`)
+}
diff --git a/openapi3/testdata/draft04.yml b/openapi3/testdata/draft04.yml
new file mode 100644
index 0000000..528272a
--- /dev/null
+++ b/openapi3/testdata/draft04.yml
@@ -0,0 +1,140 @@
+id: http://json-schema.org/draft-04/schema#
+$schema: http://json-schema.org/draft-04/schema#
+description: Core schema meta-schema
+definitions:
+  schemaArray:
+    type: array
+    minItems: 1
+    items:
+      $ref: '#'
+  positiveInteger:
+    type: integer
+    minimum: 0
+  positiveIntegerDefault0:
+    allOf:
+    - $ref: '#/definitions/positiveInteger'
+    - default: 0
+  simpleTypes:
+    enum:
+    - array
+    - boolean
+    - integer
+    - 'null'
+    - number
+    - object
+    - string
+  stringArray:
+    type: array
+    items:
+      type: string
+    minItems: 1
+    uniqueItems: true
+type: object
+properties:
+  id:
+    type: string
+  $schema:
+    type: string
+  title:
+    type: string
+  description:
+    type: string
+  default: {}
+  multipleOf:
+    type: number
+    minimum: 0
+    exclusiveMinimum: true
+  maximum:
+    type: number
+  exclusiveMaximum:
+    type: boolean
+    default: false
+  minimum:
+    type: number
+  exclusiveMinimum:
+    type: boolean
+    default: false
+  maxLength:
+    $ref: '#/definitions/positiveInteger'
+  minLength:
+    $ref: '#/definitions/positiveIntegerDefault0'
+  pattern:
+    type: string
+    format: regex
+  additionalItems:
+    anyOf:
+    - type: boolean
+    - $ref: '#'
+    default: {}
+  items:
+    anyOf:
+    - $ref: '#'
+    - $ref: '#/definitions/schemaArray'
+    default: {}
+  maxItems:
+    $ref: '#/definitions/positiveInteger'
+  minItems:
+    $ref: '#/definitions/positiveIntegerDefault0'
+  uniqueItems:
+    type: boolean
+    default: false
+  maxProperties:
+    $ref: '#/definitions/positiveInteger'
+  minProperties:
+    $ref: '#/definitions/positiveIntegerDefault0'
+  required:
+    $ref: '#/definitions/stringArray'
+  additionalProperties:
+    anyOf:
+    - type: boolean
+    - $ref: '#'
+    default: {}
+  definitions:
+    type: object
+    additionalProperties:
+      $ref: '#'
+    default: {}
+  properties:
+    type: object
+    additionalProperties:
+      $ref: '#'
+    default: {}
+  patternProperties:
+    type: object
+    additionalProperties:
+      '$ref': '#'
+    default: {}
+  dependencies:
+    type: object
+    additionalProperties:
+      anyOf:
+      - $ref: '#'
+      - $ref: '#/definitions/stringArray'
+  enum:
+    type: array
+    minItems: 1
+    uniqueItems: true
+  type:
+    anyOf:
+    - $ref: '#/definitions/simpleTypes'
+    - type: array
+      items:
+        $ref: '#/definitions/simpleTypes'
+      minItems: 1
+      uniqueItems: true
+  format:
+    type: string
+  allOf:
+    $ref: '#/definitions/schemaArray'
+  anyOf:
+    $ref: '#/definitions/schemaArray'
+  oneOf:
+    $ref: '#/definitions/schemaArray'
+  not:
+    $ref: '#'
+dependencies:
+  exclusiveMaximum:
+  - maximum
+  exclusiveMinimum:
+  - minimum
+default: {}