Repository: marshmallow-code/apispec

Issue:
Marshmallow plugin bug with string schema ref and schema_name_resolver
`resolve_schema_dict` claims to support `string` as its schema parameter type. It can get there for example when you are parsing schema from docstring using the provided yaml util, or using FlaskPlugin to do it for you. 

But, when `schema_name_resolver` returns `None` for this case, the schema gets forwarded to `schema2jsonschema` which expects a `marshmallow.Schema` instance and crashes.

Test case is a slightly modified "example application": 
<details>
<summary>Show example test case</summary>

```python
import uuid

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from apispec_webframeworks.flask import FlaskPlugin
from flask import Flask, jsonify
from marshmallow import fields, Schema


def schema_name_resolver(schema):
    if schema == "PetSchema":
        return None


# Create an APISpec
spec = APISpec(
    title="Swagger Petstore",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[
        FlaskPlugin(),
        MarshmallowPlugin(schema_name_resolver=schema_name_resolver),
    ],
)


# Optional marshmallow support
class CategorySchema(Schema):
    id = fields.Int()
    name = fields.Str(required=True)


class PetSchema(Schema):
    categories = fields.List(fields.Nested(CategorySchema))
    name = fields.Str()


# Optional Flask support
app = Flask(__name__)


@app.route("/random")
def random_pet():
    """A cute furry animal endpoint.
    ---
    get:
      description: Get a random pet
      responses:
        200:
          description: Return a pet
          content:
            application/json:
              schema: PetSchema
    """
    # Hardcoded example data
    pet_data = {
        "name": "sample_pet_" + str(uuid.uuid1()),
        "categories": [{"id": 1, "name": "sample_category"}],
    }
    return PetSchema().dump(pet_data)


# Register the path and the entities within it
with app.test_request_context():
    spec.path(view=random_pet)


@app.route("/spec.json")
def get_spec():
    return jsonify(spec.to_dict())
```
</details>

<details>
<summary>Show example stacktrace</summary>

```
Traceback (most recent call last):
  File "/home//PycharmProjects/test-apispec/main.py", line 62, in <module>
    spec.path(view=random_pet)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/core.py", line 280, in path
    plugin.operation_helper(path=path, operations=operations, **kwargs)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/__init__.py", line 202, in operation_helper
    self.resolver.resolve_response(response)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/schema_resolver.py", line 113, in resolve_response
    self.resolve_schema(response)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/schema_resolver.py", line 161, in resolve_schema
    content["schema"] = self.resolve_schema_dict(content["schema"])
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/schema_resolver.py", line 218, in resolve_schema_dict
    return self.converter.resolve_nested_schema(schema)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/openapi.py", line 96, in resolve_nested_schema
    json_schema = self.schema2jsonschema(schema)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/openapi.py", line 261, in schema2jsonschema
    fields = get_fields(schema)
  File "/home//.local/share/virtualenvs/test-apispec-HubT4B3L/lib/python3.8/site-packages/apispec/ext/marshmallow/common.py", line 63, in get_fields
    raise ValueError(
ValueError: 'PetSchema' doesn't have either `fields` or `_declared_fields`.
```
</details>

I think it should be enough to just pass `schema_instance` instead of `schema` to `self.schema2jsonschema` in `resolve_nested_schema`

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d24fdd5..475e50c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,16 @@
 Changelog
 ---------
 
+3.3.1 (unreleased)
+******************
+
+Bug fixes:
+
+- Fix ``MarshmallowPlugin`` crash when ``resolve_schema_dict`` is passed a
+  schema as string and ``schema_name_resolver`` returns ``None``
+  (:issue:`566`). Thanks :user:`black3r` for reporting and thanks
+  :user:`Bangterm` for the PR.
+
 3.3.0 (2020-02-14)
 ******************
 
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 6cc5bba..b3740a3 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -106,7 +106,7 @@ To run syntax checks: ::
 Documentation
 +++++++++++++
 
-Contributions to the documentation are welcome. Documentation is written in `reStructuredText`_ (rST). A quick rST reference can be found `here <https://docutils.sourceforge.io/docs/user/rst/quickref.html>`_. Builds are powered by Sphinx_.
+Contributions to the documentation are welcome. Documentation is written in `reStructured Text`_ (rST). A quick rST reference can be found `here <http://docutils.sourceforge.net/docs/user/rst/quickref.html>`_. Builds are powered by Sphinx_.
 
 To build the docs in "watch" mode: ::
 
@@ -115,5 +115,5 @@ To build the docs in "watch" mode: ::
 Changes in the `docs/` directory will automatically trigger a rebuild.
 
 .. _Sphinx: http://sphinx.pocoo.org/
-.. _`reStructuredText`: https://docutils.sourceforge.io/rst.html
+.. _`reStructured Text`: http://docutils.sourceforge.net/rst.html
 .. _`apispec`: https://github.com/marshmallow-code/apispec
diff --git a/setup.py b/setup.py
index a8a3aab..14dc960 100644
--- a/setup.py
+++ b/setup.py
@@ -4,11 +4,11 @@ from setuptools import setup, find_packages
 EXTRAS_REQUIRE = {
     "yaml": ["PyYAML>=3.10"],
     "validation": ["prance[osv]>=0.11"],
-    "lint": ["flake8==3.8.2", "flake8-bugbear==20.1.4", "pre-commit~=2.4"],
+    "lint": ["flake8==3.8.1", "flake8-bugbear==20.1.4", "pre-commit~=2.4"],
     "docs": [
         "marshmallow>=2.19.2",
         "pyyaml==5.3.1",
-        "sphinx==3.0.4",
+        "sphinx==3.0.3",
         "sphinx-issues==1.2.0",
         "sphinx-rtd-theme==0.4.3",
     ],
diff --git a/src/apispec/ext/marshmallow/openapi.py b/src/apispec/ext/marshmallow/openapi.py
index 8cf3460..a79172d 100644
--- a/src/apispec/ext/marshmallow/openapi.py
+++ b/src/apispec/ext/marshmallow/openapi.py
@@ -93,7 +93,7 @@ class OpenAPIConverter(FieldConverterMixin):
             name = self.schema_name_resolver(schema)
             if not name:
                 try:
-                    json_schema = self.schema2jsonschema(schema)
+                    json_schema = self.schema2jsonschema(schema_instance)
                 except RuntimeError:
                     raise APISpecError(
                         "Name resolver returned None for schema {schema} which is "


Test patch:
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index a5a91bd..4755ad7 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -558,6 +558,55 @@ class TestOperationHelper:
             "schema"
         ] == build_ref(spec, "schema", "Pet")
 
+    @pytest.mark.parametrize(
+        "pet_schema", (PetSchema, PetSchema(), "tests.schemas.PetSchema"),
+    )
+    def test_schema_name_resolver_returns_none_v2(self, pet_schema):
+        def resolver(schema):
+            return None
+
+        spec = APISpec(
+            title="Test resolver returns None",
+            version="0.1",
+            openapi_version="2.0",
+            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
+        )
+        spec.path(
+            path="/pet",
+            operations={"get": {"responses": {200: {"schema": pet_schema}}}},
+        )
+        get = get_paths(spec)["/pet"]["get"]
+        assert "properties" in get["responses"]["200"]["schema"]
+
+    @pytest.mark.parametrize(
+        "pet_schema", (PetSchema, PetSchema(), "tests.schemas.PetSchema"),
+    )
+    def test_schema_name_resolver_returns_none_v3(self, pet_schema):
+        def resolver(schema):
+            return None
+
+        spec = APISpec(
+            title="Test resolver returns None",
+            version="0.1",
+            openapi_version="3.0.0",
+            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
+        )
+        spec.path(
+            path="/pet",
+            operations={
+                "get": {
+                    "responses": {
+                        200: {"content": {"application/json": {"schema": pet_schema}}}
+                    }
+                }
+            },
+        )
+        get = get_paths(spec)["/pet"]["get"]
+        assert (
+            "properties"
+            in get["responses"]["200"]["content"]["application/json"]["schema"]
+        )
+
     @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
     def test_schema_uses_ref_in_parameters_and_request_body_if_available_v2(
         self, spec_fixture