Repository: bentoml/BentoML

Issue:
bug: OpenAPI schema components not included for MultiPart IODescriptor with JSON Input
### Describe the bug

When creating an API endpoint with a Multipart Input, where one of the child inputs is JSON with a pydantic model with an Enum field, the resulting OpenAPI spec does not include the schema component for the Enum field.

I believe I have narrowed it down to this line: https://github.com/bentoml/BentoML/blob/main/src/bentoml/_internal/io_descriptors/multipart.py#L221

Changing it to something like:
```python
    def openapi_components(self) -> dict[str, t.Any] | None:
        components = {}

        for io in self._inputs.values():
            child_components = io.openapi_components()
            if child_components is not None:
                components.update(child_components)

        return components
```
Fixes the issue for me, ~though I'm not sure if that would break for inputs other than Json.~ Looks like only the JSON input ever returns a value from `openapi_components` so I believe this is a safe change and I've opened a PR 

### To reproduce

The following service file will reproduce the issue:
```python
from enum import Enum

from bentoml.io import JSON, Image, Multipart, Text
import bentoml
import pydantic

svc = bentoml.Service(name="test", runners=[])

class FruitEnum(str, Enum):
    pear = 'pear'
    banana = 'banana'


class Params(pydantic.BaseModel):
    fruit: FruitEnum = FruitEnum.pear

@svc.api(input=Multipart(image=Image(), params=JSON(pydantic_model=Params)), output=Text())
def inference(image, params):
    return "Hello"
```

After running `bentoml serve` and navigating to http://localhost:3000, if you expand the "/predict" endpoint documentation you will get an error at the top of the page (you may need to scroll back up) saying:
```
Resolver error at paths./inference.post.requestBody.content.multipart/form-data.schema.properties.params.properties.fruit.allOf.0.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/FruitEnum does not exist in document
Resolver error at paths./inference.post.requestBody.content.multipart/form-data.schema.properties.params.properties.fruit.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/FruitEnum does not exist in document
```
And if you view http://localhost:3000/docs.json you can see that there is no schema for the `FruitEnum`

I have confirmed that with the code change I mentioned above, there is no error and the FruitEnum schema is present in the docs.json file.

### Expected behavior

There should be no error and the OpenAPI spec should contain all of the components from the Multipart input's children. The rendered OpenAPI spec should be sufficient to generate a client for the BentoML service.



### Environment

#### Environment variable

```bash
BENTOML_DEBUG=''
BENTOML_QUIET=''
BENTOML_BUNDLE_LOCAL_BUILD=''
BENTOML_DO_NOT_TRACK=''
BENTOML_CONFIG=''
BENTOML_CONFIG_OPTIONS=''
BENTOML_PORT=''
BENTOML_HOST=''
BENTOML_API_WORKERS=''
```

#### System information

`bentoml`: 1.0.12
`python`: 3.10.6
`platform`: Linux-5.15.0-57-generic-x86_64-with-glibc2.35
`uid_gid`: 1000:1000
<details><summary><code>pip_packages</code></summary>

<br>

```
aiohttp==3.8.3
aiosignal==1.3.1
anyio==3.6.2
appdirs==1.4.4
asgiref==3.6.0
async-timeout==4.0.2
attrs==22.2.0
backoff==2.2.1
bentoml==1.0.12
build==0.10.0
cattrs==22.2.0
certifi==2022.12.7
charset-normalizer==2.1.1
circus==0.18.0
click==8.1.3
click-option-group==0.5.5
cloudpickle==2.2.0
commonmark==0.9.1
contextlib2==21.6.0
deepmerge==1.1.0
Deprecated==1.2.13
exceptiongroup==1.1.0
filetype==1.2.0
frozenlist==1.3.3
fs==2.4.16
googleapis-common-protos==1.58.0
h11==0.14.0
idna==3.4
Jinja2==3.1.2
MarkupSafe==2.1.1
multidict==6.0.4
numpy==1.24.1
opentelemetry-api==1.14.0
opentelemetry-exporter-otlp-proto-http==1.14.0
opentelemetry-instrumentation==0.35b0
opentelemetry-instrumentation-aiohttp-client==0.35b0
opentelemetry-instrumentation-asgi==0.35b0
opentelemetry-proto==1.14.0
opentelemetry-sdk==1.14.0
opentelemetry-semantic-conventions==0.35b0
opentelemetry-util-http==0.35b0
packaging==21.3
pathspec==0.10.3
Pillow==9.4.0
pip-requirements-parser==32.0.1
pip-tools==6.12.1
prometheus-client==0.15.0
protobuf==3.20.3
psutil==5.9.4
pydantic==1.10.4
Pygments==2.14.0
pynvml==11.4.1
pyparsing==3.0.9
pyproject_hooks==1.0.0
python-dateutil==2.8.2
python-json-logger==2.0.4
python-multipart==0.0.5
PyYAML==6.0
pyzmq==25.0.0
requests==2.28.2
rich==13.1.0
schema==0.7.5
simple-di==0.1.5
six==1.16.0
sniffio==1.3.0
starlette==0.23.1
tomli==2.0.1
tornado==6.2
typing_extensions==4.4.0
urllib3==1.26.14
uvicorn==0.20.0
watchfiles==0.18.1
wrapt==1.14.1
yarl==1.8.2
```

</details>



Discussion:

Resolving patch:
diff --git a/src/bentoml/_internal/io_descriptors/multipart.py b/src/bentoml/_internal/io_descriptors/multipart.py
--- a/src/bentoml/_internal/io_descriptors/multipart.py
+++ b/src/bentoml/_internal/io_descriptors/multipart.py
@@ -219,7 +219,14 @@ def openapi_schema(self) -> Schema:
         )
 
     def openapi_components(self) -> dict[str, t.Any] | None:
-        pass
+        components = {}
+
+        for io in self._inputs.values():
+            child_components = io.openapi_components()
+            if child_components is not None:
+                components.update(child_components)
+
+        return components
 
     def openapi_example(self) -> t.Any:
         return {args: io.openapi_example() for args, io in self._inputs.items()}


Test patch: