metaclass-nl / filter-bundle

Filter bundle for API Platform, Filter Logic
MIT License
50 stars 9 forks source link

OpenApi docs/specs #11

Open benblub opened 2 years ago

benblub commented 2 years ago

The filters are not displayed in the OpenApi docs. I would still find it a useful extension. What do you think?

metaclass-nl commented 2 years ago

It would certanily be nice if the options to combine existing filters with and, or and not would be displayed in the OpenApi docs!

I was led to believe that the OpenApi docs only support a flat list of keys that activate the filters and types of the values that can be passed to them. But i must admit, i did not really look into it. So maybe OpenApi supports more and can actually express the combination of the existing filters woth and, or and not in a tree of indefinite depth. If you have an idea about how to do that, please explain!

Or maybe a flat list of a configurable number of layers combined with the specs of each of the existing filters would do? But if there are many filters, wouldn't that lead to a very long list? How to keep that within usable limits?

yobottehg commented 2 years ago

We translate the schema to typescript types using https://www.npmjs.com/package/openapi-typescript. For that purpose a wildcard for any additional "and", "or" and "not" would be sufficient for the beginning. Like that the consuming client knows after the FilterLogic filter possibilities.

Something in this direction : https://github.com/drwpow/openapi-typescript/commit/f9229a2e7da9e6b8ac5375577c6194598a7cab91#diff-0865c2993ba1a2e26419456a46efc98132e5afdffe4028442d17945ba7c19826R21

For that purpose i think we need to describe the and, or and not as query parameters: https://swagger.io/docs/specification/describing-parameters/

The open api spec in 3.0 at least describes this "additional properties" here: https://spec.openapis.org/oas/v3.0.3#schema-object

Another solution would be to add "valid" open api exposed filters based on allowed filters on the api resource using FilterLogic. So we map search filters for title and description like this:

Would perhaps need to settle on one way how to apply these filters not not allow arbitrary combinations.

metaclass-nl commented 2 years ago

Hi,

I do not have the time right now to look into it. Ik hope next week is less busy so that i can take a look.

Thanks for now, Henk.

metaclass-nl commented 1 year ago

Flattening out the combinations of logic and filters, like you do under "Another solution" would be relatively easy but limiting the logic to those specified would be harder and goes against the idea of allowing the client to specify the logic. Something like this is discussed on Filters RFC and here. But not limiting the logic would necessarily leave most options undocumented. Could still be usefull though because Swagger Ui would understand the part that IS documented.

I do not understand what you mean by a wildcard, a description like { [key: string]: unknown } does not seem valid to me.

Reading swagger.io/docs my best guess would be the following:

parameters: [
    (..),
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            type: "object"
        }
        style: deepObject
        explode: false,
        allowEmptyValue: true
    },
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            type: "object"
        }
        style: deepObject
        explode: false,
        allowEmptyValue: false
    },
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            type: "object"
        }
        style: deepObject
        explode: false,
        allowEmptyValue: false
    },
}

Objects additionalProperties by default is true, but i am not sure type "object" itself would be valid. Instead one could type and, or and not as "#/components/schemas/LogicalFilters" defined as:

parameters: [
    (..),
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            { $ref: "#/components/schemas/LogicalFilters" }
        },
        style: deepObject,
        explode: false,
        allowEmptyValue: true
    },
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            { $ref: "#/components/schemas/LogicalFilters" }
        },
        style: deepObject,
        explode: false,
        allowEmptyValue: false
    },
    {
        name: "and",
        in: "query",
        description: "Combines the nested filter specs through AND"
        required: false,
        schema: {
            { $ref: "#/components/schemas/LogicalFilters" }
        },
        style: deepObject,
        explode: false,
        allowEmptyValue: false
    },
}
LogicalFilters: {
    type: "object",
    description: "Recursive logical combination with the filter specs as additional properties",
    properties: {
        and: {
            nullable: true,
            description: "Combines the nested filter specs through AND",
            anyOf: [
                { $ref: "#/components/schemas/LogicalFilters" }
            ] 
        },
        or: {
            nullable: true,
            description: "Combines the nested filter specs through OR",
            anyOf: [
                { $ref: "#/components/schemas/LogicalFilters" }
            ] 
        },
        not: {
            nullable: true,
            description: "Negates the nested filter specs",
            anyOf: [
                { $ref: "#/components/schemas/LogicalFilters" }
            ] 
        }   
    }
}

Or one could make the type specific to the entity and its filters. For example the Employee entity from MetaClass Tutorial Api Platform only has one filter that reacts to the parameter "search":

Employee.logical-filter: {
    type: "object",
    description: "Recursive logical combination with filter specs"
    properties: {
        and: {
            nullable: true,
            description: "Combines the underlying filter specs through AND",
            anyOf: [
                { $ref: "#/components/schemas/Employee.logical-filter" } 
            ] 
        },
        or: {
            nullable: true,
            description: "Combines the underlying filter specs through OR",
            anyOf: [
                { $ref: "#/components/schemas/Employee.logical-filter" }
            ] 
        },
        not: {
            nullable: true,
            description: "Negates the underlying filter specs",
            anyOf: [
                { $ref: "#/components/schemas/Employee.logical-filter" }
            ] 
        },
        search: {
            nullable: true,
            description: "Selects entities where each search term is found somewhere in at least one of the specified properties",
            type: "string",
        }
    }
}

But i am not sure these are valid docs and even then they do not cover multiple criteria for the same property like this:

/offers/?and[price]=10&and[][description]=shirt&and[][description]=cotton

I would not know how to document this with openapi.