diff --git a/royalnet/backpack/stars/api_login_royalnet.py b/royalnet/backpack/stars/api_login_royalnet.py index 32c04d28..be6fda94 100644 --- a/royalnet/backpack/stars/api_login_royalnet.py +++ b/royalnet/backpack/stars/api_login_royalnet.py @@ -11,6 +11,13 @@ class ApiLoginRoyalnetStar(ApiStar): methods = ["POST"] + summary = "Login as a Royalnet user, creating a 7-day login token." + + parameters = { + "username": "The name of the user you are logging in as.", + "password": "The password of the user you are logging in as." + } + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) UserT = self.alchemy.get(User) diff --git a/royalnet/backpack/stars/api_royalnet_version.py b/royalnet/backpack/stars/api_royalnet_version.py index 74092984..d5290097 100644 --- a/royalnet/backpack/stars/api_royalnet_version.py +++ b/royalnet/backpack/stars/api_royalnet_version.py @@ -6,6 +6,8 @@ import royalnet.utils as ru class ApiRoyalnetVersionStar(ApiStar): path = "/api/royalnet/version/v1" + summary = "Get the current Royalnet version." + async def api(self, data: ApiData) -> ru.JSON: return { "semantic": rv.semantic diff --git a/royalnet/backpack/stars/api_token_create.py b/royalnet/backpack/stars/api_token_create.py index 4c1f6211..9efcba1c 100644 --- a/royalnet/backpack/stars/api_token_create.py +++ b/royalnet/backpack/stars/api_token_create.py @@ -10,6 +10,13 @@ class ApiTokenCreateStar(ApiStar): methods = ["POST"] + summary = "Create a new login token of any duration." + + parameters = { + "token": "Your current login token.", + "duration": "The duration in seconds of the new token." + } + async def api(self, data: ApiData) -> ru.JSON: user = await data.user() try: diff --git a/royalnet/backpack/stars/api_token_info.py b/royalnet/backpack/stars/api_token_info.py index 7174df36..9ed0a012 100644 --- a/royalnet/backpack/stars/api_token_info.py +++ b/royalnet/backpack/stars/api_token_info.py @@ -5,6 +5,12 @@ from royalnet.constellation.api import * class ApiTokenInfoStar(ApiStar): path = "/api/token/info/v1" + summary = "Get info about a login token." + + parameters = { + "token": "The login token to get info about.", + } + async def api(self, data: ApiData) -> ru.JSON: token = await data.token() return token.json() diff --git a/royalnet/backpack/stars/api_token_passwd.py b/royalnet/backpack/stars/api_token_passwd.py index 323dd930..0450fe39 100644 --- a/royalnet/backpack/stars/api_token_passwd.py +++ b/royalnet/backpack/stars/api_token_passwd.py @@ -11,6 +11,13 @@ class ApiTokenPasswdStar(ApiStar): methods = ["POST"] + summary = "Change Royalnet password for an user." + + parameters = { + "token": "Your current login token.", + "new_password": "The password you want to set." + } + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) token = await data.token() diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index afa58fc2..305c58a2 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -11,6 +11,12 @@ import royalnet.utils as ru class ApiStar(PageStar, ABC): + summary: str = "" + + description: str = "" + + parameters: Dict[str, str] = {} + async def page(self, request: Request) -> JSONResponse: if request.query_params: data = request.query_params @@ -40,3 +46,39 @@ class ApiStar(PageStar, ABC): async def api(self, data: ApiData) -> ru.JSON: raise NotImplementedError() + + @classmethod + def swagger(cls) -> str: + """Generate one or more swagger paths for this ApiStar.""" + string = [f'{cls.path}:\n'] + for method in cls.methods: + string.append( + f' {method.lower()}:\n' + f' summary: "{cls.summary}"\n' + f' description: "{cls.description}"\n' + f' produces:\n' + f' - "application/json"\n' + f' responses:\n' + f' 200:\n' + f' description: "Success"\n' + f' 400:\n' + f' description: "Bad request"\n' + f' 403:\n' + f' description: "Forbidden"\n' + f' 404:\n' + f' description: "Not found"\n' + f' 500:\n' + f' description: "Serverside unhandled exception"\n' + f' 501:\n' + f' description: "Not yet implemented"\n' + ) + if len(cls.parameters) > 0: + string.append(f' parameters:\n') + for parameter in cls.parameters: + string.append( + f' - name: "{parameter}"\n' + f' in: "query"\n' + f' description: "{cls.parameters[parameter]}"\n' + f' type: "string"\n' + ) + return "".join(string).rstrip("\n") diff --git a/royalnet/generate.py b/royalnet/generate.py index 80e18944..ff7105de 100644 --- a/royalnet/generate.py +++ b/royalnet/generate.py @@ -1,6 +1,7 @@ import toml import importlib import click +from .version import semantic p = click.echo @@ -46,6 +47,29 @@ def run(config_filename, file_format): for line in lines: p(line) + elif file_format == "swagger": + p( + f'swagger: "2.0"\n' + f'info:\n' + f' description: "This is autogenerated Royalnet API documentation."\n' + f' title: "Royalnet"\n' + f' version: "{semantic}"\n' + f'paths:' + ) + for pack_name in packs: + pack = packs[pack_name] + + try: + page_stars = pack["stars"].available_page_stars + except AttributeError: + p(f"Pack `{pack}` does not have the `available_page_stars` attribute.", err=True) + continue + for page_star in page_stars: + try: + p(f' {page_star.swagger()}') + except AttributeError: + pass + else: raise click.ClickException("Unknown format")