apistar icon indicating copy to clipboard operation
apistar copied to clipboard

Selecting a server or setting a custom endpoint

Open Lucidiot opened this issue 5 years ago • 4 comments

OpenAPI 3 allows for multiple Server Objects to be defined on a single document; however, it is not currently possible to select which server is wanted with the APIStar client, nor it is to set a custom base URL, except with setting the URL on the schema document itself and updating every Link URL.

I guess adding a base_url=... keyword argument to apistar.Client isn't hard, but how should one select an existing server from the schema, or list them? Currently, the URL from the first server is used: https://github.com/encode/apistar/blob/master/apistar/schemas/openapi.py#L357

Lucidiot avatar Apr 03 '19 13:04 Lucidiot

Yup - not really sure just yet.

Thanks for digging into all this so much!

I've a bit pushed for time right now so might not be reviewing apistar tickets for the next few days, but I'll get back onto it in due course.

tomchristie avatar Apr 05 '19 10:04 tomchristie

I just ran into this too.

According to the spec for the paths object, the path should be appended to the server object's url

The field name MUST begin with a slash. The path is appended (no relative URL resolution) to the expanded URL from the Server Object's url field in order to construct the full URL.

but OpenAPI.get_link uses urljoin to set the Link's url, which strips any path (like /api/v1) off the url that comes from the server object. https://github.com/encode/apistar/blob/master/apistar/schemas/openapi.py#L465

MattFisher avatar Jun 04 '19 06:06 MattFisher

+1

I tried to avoid this by stripping leading / in paths as follows, but this caused typesystem.base.ValidationError.

doc['paths'] = {
    path.lstrip('/'): path_item_obj
    for path, path_item_obj in doc['paths'].items()
}
apistar.Client(doc)

This is because properties of Paths object are required leading /. https://github.com/encode/apistar/blob/2edeb694/apistar/schemas/openapi.py#L102

pjxiao avatar Feb 05 '20 06:02 pjxiao

The required leading slash is a requirement from OpenAPI itself (see the Paths Object specs).

Here is the workaround we used:

from urllib.parse import urlsplit

class MyClient(apistar.Client):

    def __init__(self, *args, base_url=None, **kwargs):
        # Let APIStar do its parsing
        super().__init__(*args, **kwargs)

        # Strip scheme, hostname and absolute path from all link URLs
        for link_info in self.document.walk_links():
            original_url = urlsplit(link_info.link.url)
            new_url = ('', '', *original_url[2:])
            link_info.link.url = urlunsplit(new_url).lstrip('/')

        if base_url:
            # Ensure the base URL ends with a slash to prevent issues:
            # urljoin('http://a/b', 'c') → http://a/c
            # urljoin('http://a/b/', 'c') → http://a/b/c
            if not base_url.endswith('/'):
                base_url += '/'

            self.document.base_url = base_url

This goes through every parsed link in the Document instance to remove the scheme, hostname and leading slash, resulting in every API endpoint having a relative URL (a/b/c instead of http://myserver.com/a/b/c), then updates Document.url to a custom base URL when specified. This then allows Client.get_url to still use urljoin and do any of its checks, but with any base URL, including custom base paths.

Lucidiot avatar Feb 05 '20 06:02 Lucidiot