Repository: Algebra8/pyopenapi3

Issue:
Unresolvable pointer due to wrong reference
Consider the following example:


```
@component.parameter
class PetId:

    name = "pet_id"
    description = "Pet's Unique Identifier"
    in_field = "path"
    schema = create_schema(String, pattern="^[a-zA-Z0-9-]+$")
    required = True


@open_bldr.path
class PetsWithId:

    path = "/pets/{pet_id:PetId}"
```

The output of this build, in json (arbitrary), will be the following:

```
...
      "parameters": [
        {
          "name": "pet_id",
          "in": "path",
          "required": true,
          "schema": {
            "$ref": "#/components/schemas/PetId"
          }
        }
      ]
...
```
However, its definition will not be in `components/schemas`, it will be in `components/parameters`:

```
"components":
    ...
    "parameters": {
      "PetId": {
        "name": "pet_id",
        "in": "path",
        "description": "Pet's Unique Identifier",
        "required": true,
        "schema": {
          "pattern": "^[a-zA-Z0-9-]+$",
          "type": "string"
        }
...
```

Need to make this reference point to parameters.



Discussion:

Patch:
diff --git a/src/pyopenapi3/builders.py b/src/pyopenapi3/builders.py
index 164f041..a4b1663 100644
--- a/src/pyopenapi3/builders.py
+++ b/src/pyopenapi3/builders.py
@@ -5,7 +5,7 @@ import re
 
 import yaml
 
-from pyopenapi3.data_types import Component
+from pyopenapi3.data_types import Component, Parameters, Schemas
 from pyopenapi3.utils import (
     build_mediatype_schema_from_content,
     create_schema,
@@ -500,14 +500,14 @@ class ComponentBuilder:
         # Flush functions that were used to build this ObjectSchema.
         self._field_builds = {}
 
-        return inject_component(cls)
+        return inject_component(cls, cmp_type=Schemas)
 
     def _parameters(self, cls=None, /, *, as_dict=None):
         if cls is not None:
             self._parameter_builds[cls.__name__] = \
                 ParamBuilder.build_param_from_cls(cls)
 
-            injected_comp_cls = inject_component(cls)
+            injected_comp_cls = inject_component(cls, cmp_type=Parameters)
             # Allow `cls` to be a valid reference in formatted `path`
             # on `PathItemBuilder`
             _allowed_types[cls.__name__] = injected_comp_cls
diff --git a/src/pyopenapi3/data_types.py b/src/pyopenapi3/data_types.py
index 530f886..bdb8bab 100644
--- a/src/pyopenapi3/data_types.py
+++ b/src/pyopenapi3/data_types.py
@@ -151,3 +151,29 @@ class Object(Primitive):
 # Components (Custom defined objects)
 class Component(OpenApiObject):
     ...
+
+
+class Schemas(Component):
+    """Schema component type.
+
+    Note, the name of this class is important, as it will
+    determine the composition the Components schema.
+
+    I.e.:
+
+        "components":
+            "schemas": ...
+    """
+
+
+class Parameters(Component):
+    """Parameter component type.
+
+    Note, the name of this class is important, as it will
+    determine the composition the Components schema.
+
+    I.e.:
+
+        "components":
+            "parameters": ...
+    """
diff --git a/src/pyopenapi3/utils.py b/src/pyopenapi3/utils.py
index 557bcc2..ff70d52 100644
--- a/src/pyopenapi3/utils.py
+++ b/src/pyopenapi3/utils.py
@@ -10,7 +10,8 @@ from typing import (
     Dict,
     List,
     Generator,
-    Iterable
+    Iterable,
+    TypeVar
 )
 from string import Formatter
 
@@ -22,7 +23,7 @@ from .data_types import (
     Array,
     Field,
     Primitive,
-    Component
+    Component,
 )
 # Used to dynamically retrieve field in `parse_name_and_type
 # _from_fmt_str`
@@ -56,9 +57,6 @@ from .schemas import (
 )
 
 
-OPENAPI_DEF = '__OPENAPIDEF__FIELD_OR_COMPONENT__'
-
-
 class _ObjectToDTSchema:
 
     # Strings
@@ -167,18 +165,11 @@ def parse_name_and_type_from_fmt_str(
                 ) from None
 
 
-def create_reference(name: str) -> ReferenceObject:
-    return ReferenceObject(ref=f"#/components/schemas/{name}")
-
-
-def mark_component_and_attach_schema(obj, schema):
-    """Mark an object as relating to an Open API schema
-    and attach said schema to it.
-
-    This will be used by `create_object` to build the entire
-    `ObjectSchema`.
-    """
-    setattr(obj, OPENAPI_DEF, schema)
+def create_reference(
+    name: str,
+    component_dir: str = "schemas"
+) -> ReferenceObject:
+    return ReferenceObject(ref=f"#/components/{component_dir}/{name}")
 
 
 def create_schema(
@@ -201,9 +192,19 @@ def create_schema(
 
 
 def convert_objects_to_schema(obj: Type[Component]) -> ReferenceObject:
-    # Any non-reference object should be created by the
-    # Components builder.
-    return create_reference(obj.__name__)
+    """Convert a custom object to a schema.
+
+    This is done by create a reference to the object. Any non-reference
+    object should be created by the Components builder.
+
+    param `obj` **must** be a subtype of `data_types.Component`. Its
+    type will determine what kind of component it is, e.g. '#/components/
+    schemas/...' or '#/components/parameters/...'.
+    """
+    cmp_type: str = 'schemas'  # default component type
+    if hasattr(obj, '__cmp_type__'):
+        cmp_type = obj.__cmp_type__.lower()  # type: ignore
+    return create_reference(obj.__name__, cmp_type)
 
 
 def convert_primitive_to_schema(
@@ -232,22 +233,25 @@ def convert_array_to_schema(
             return schema_type(items=sub_schemas[0], **kwargs)
 
 
-def inject_component(cls):
+ComponentType = TypeVar('ComponentType', bound=Component)
+
+
+def inject_component(cls, cmp_type: Type[ComponentType]):
     """'Inject' the `Component` class into the custom, user defined,
     soon-to-be Component, class.
 
     This will help when building a property that involves a user defined
     custom Component.
+
+    param `cmp_type` is some subtype of `data_types.Component`, e.g.
+    whether it is a Schema component or Parameter component.
     """
     if issubclass(cls, Component):
         return cls
     else:
-        # @functools.wraps(cls, updated=())
-        # class Injected(cls, Component):
-        #     pass
         injected = type(
             "Injected",
-            (cls, Component),
+            (cls, cmp_type),
             {attr_name: attr for attr_name, attr in cls.__dict__.items()}
         )
         injected.__qualname__ = f'Component[{cls.__name__}]'
@@ -255,6 +259,7 @@ def inject_component(cls):
         # used in the conversion to an Open API object, e.g.
         # {__name__: <rest of properties>}.
         injected.__name__ = cls.__name__
+        injected.__cmp_type__ = cmp_type.__name__  # type: ignore
 
         return injected
 


Test patch:
diff --git a/tests/examples/component.py b/tests/examples/component.py
index e74bea9..50552e2 100644
--- a/tests/examples/component.py
+++ b/tests/examples/component.py
@@ -126,3 +126,27 @@ object_lvl_test = {
     }
   }
 }
+
+param_reference_comp = {
+  "schemas": {
+    "Pet": {
+      "type": "object",
+      "properties": {
+        "pet_id": {
+          "$ref": "#/components/parameters/PetId"
+        }
+      }
+    }
+  },
+  "parameters": {
+    "PetId": {
+      "name": "pet_id",
+      "in": "path",
+      "description": "Pet's Unique Identifier",
+      "schema": {
+        "pattern": "^[a-zA-Z0-9-]+$",
+        "type": "string"
+      }
+    }
+  }
+}
diff --git a/tests/examples/path.py b/tests/examples/path.py
index e23760b..753d6fc 100644
--- a/tests/examples/path.py
+++ b/tests/examples/path.py
@@ -27,7 +27,7 @@ path_with_parameter = {
       'name': 'pet_id',
       'in': 'path',
       'required': True,
-      'schema': {'$ref': '#/components/schemas/PetID'}
+      'schema': {'$ref': '#/components/parameters/PetID'}
     }
   ]
 }
diff --git a/tests/test_builders.py b/tests/test_builders.py
index a0b788f..339e93e 100644
--- a/tests/test_builders.py
+++ b/tests/test_builders.py
@@ -357,3 +357,25 @@ def test_component_with_inline_object():
             }
         }
     }
+
+
+def test_component_parameter_references():
+    """Test that a component parameter gets referenced correctly."""
+    c = ComponentBuilder()
+
+    @c.parameter
+    class PetId:
+
+        name = "pet_id"
+        description = "Pet's Unique Identifier"
+        in_field = "path"
+        schema = create_schema(String, pattern="^[a-zA-Z0-9-]+$")
+
+    @c.schema
+    class Pet:
+
+        @c.schema_field
+        def pet_id(self) -> PetId:
+            ...
+
+    assert c.build.dict() == component_examples.param_reference_comp