alysivji / falcon-apispec

apispec plugin that generates OpenAPI specification (aka Swagger Docs) for Falcon web applications.
MIT License
44 stars 31 forks source link

Add support for suffix="collection" routes (Ref #18) #28

Closed Matt-Murungi closed 3 years ago

Matt-Murungi commented 3 years ago

@hharutyunyan the issue now is that only one path per resource is mapped. All paths should be mapped for a given resource since that's why suffixes were added to Falcon in the first place.

I'm thinking calls to spec.path for multiple paths should look like:

spec.path('/accounts', resource=accounts)
spec.path('/accounts/{account_id}', resource=accounts)

And the plugin should be more like:

class FalconPlugin(BasePlugin):
    """APISpec plugin for Falcon"""

    def __init__(self, app):
        super(FalconPlugin, self).__init__()
        self._app = app

    @staticmethod
    def _generate_resource_uri_mapping(app):
        routes_to_check = copy.copy(app._router._roots)

        mapping = {}
        for route in routes_to_check:
            uri = route.uri_template
            resource = route.resource
            if resource not in mapping:
                mapping[resource] = {}
            if uri not in mapping[resource]:
                mapping[resource][uri] = {}

            if route.method_map:
                for method_name, method_handler in route.method_map.items():
                    if method_handler.__dict__.get("__module__") == "falcon.responders":
                        continue
                    mapping[resource][uri][method_name.lower()] = method_handler

            routes_to_check.extend(route.children)
        return mapping

    def path_helper(self, path, operations, resource, base_path=None, **kwargs):
        """Path helper that allows passing a Falcon resource instance."""
        resource_uri_mapping = self._generate_resource_uri_mapping(self._app)
        if resource not in resource_uri_mapping:
            raise APISpecError("Could not find endpoint for resource {0}".format(resource))
        if path not in resource_uri_mapping[resource]:
            raise APISpecError("Could not find path {0} for resource {1}".format(path, resource))

        operations.update(yaml_utils.load_operations_from_docstring(resource.__doc__) or {})

        if base_path is not None:
            # make sure base_path accept either with or without leading slash                            
            # swagger 2 usually come with leading slash but not in openapi 3.x.x                         
            base_path = '/' + base_path.strip('/')
            path = re.sub(base_path, "", path, 1)

        methods = resource_uri_mapping[resource][path]

        for method_name, method_handler in methods.items():
            docstring_yaml = yaml_utils.load_yaml_from_docstring(method_handler.__doc__)
            operations[method_name] = docstring_yaml or dict()
        return path

Originally posted by @chretienmn in https://github.com/alysivji/falcon-apispec/issues/18#issuecomment-637891479

Sylphe88 commented 3 years ago

APISpec does not support mapping a path to multiple methods of the same type (e.g. 2 GETs), and falcon-apispec binds a resource to a path at registration, so supporting this may prove to be much more challenging than it looks.

We could pass an additional kwarg to spec.path(resource=...) to specify we'd like to register the suffix'ed method(s)?