Beispiel #1
0
def _signature_set_oas_gone(
    headers_arg: t.Any,
) -> t.Tuple[t.Set[exceptions.Error], model.ParamMapping]:
    logger.debug('"headers" found in signature but not in operation')

    errors: t.Set[exceptions.Error] = set()
    param_mapping: t.Dict[model.OASParam, model.FunctionArgName] = {}

    try:
        r_headers_arg = types.resolve_root_type(headers_arg)
        entries = t.get_type_hints(r_headers_arg).items()

        logger.debug(
            'headers_arg is {h}:{t} with entries {e}',
            h=r_headers_arg,
            e=entries,
            t=type(r_headers_arg),
        )

        for hdr_param_name, hdr_param_type in entries:
            if hdr_param_name not in RESERVED_HEADERS:
                logger.error(
                    '{sig_key} is not one of {reserved_headers} headers',
                    sig_key=hdr_param_name,
                    reserved_headers=oas.OASReservedHeaders,
                )
                errors.add(
                    exceptions.Error(
                        param_name=f'headers.{hdr_param_name}',
                        reason='unknown',
                    ),
                )
            elif hdr_param_type != str:
                errors.add(
                    exceptions.Error(
                        param_name=f'headers.{hdr_param_name}',
                        reason=exceptions.IncorrectTypeReason(
                            actual=hdr_param_type,
                            expected=[str],
                        ),
                    ),
                )
            else:
                param_key = model.get_f_param(hdr_param_name)
                param_mapping[model.OASParam(
                    param_in='header',
                    param_name=RESERVED_HEADERS[param_key],
                )] = param_key
    except TypeError:
        # deal with mapping: in that case user will receive all
        # reserved headers inside of the handler
        for hdr_f_name, hdr_name in RESERVED_HEADERS.items():
            param_mapping[model.OASParam(
                param_in='header',
                param_name=hdr_name.lower(),
            )] = hdr_f_name

    return errors, param_mapping
Beispiel #2
0
def analyze(
    parameters: t.Sequence[oas.OASParameter],
    signature: t.Dict[str, t.Type[t.Any]],
) -> t.Tuple[t.Set[exceptions.Error], model.ParamMapping]:
    errors: t.Set[exceptions.Error] = set()
    param_mapping: t.Dict[model.OASParam, model.FunctionArgName] = {}

    for op_param in parameters:
        logger.debug('Analyzing parameter={p}', p=op_param)
        try:
            handler_param_name = model.get_f_param(op_param.name)

            handler_param_type = signature.pop(handler_param_name)
            op_param_type = model.convert_oas_param_to_ptype(op_param)

            logger.opt(lazy=True).trace(
                'parameter={p} => p_type={p_type} f_type={f_type}',
                p=lambda: op_param,
                p_type=lambda: op_param_type,
                f_type=lambda: handler_param_type,
            )

            if handler_param_type != op_param_type:
                errors.add(
                    exceptions.Error(
                        param_name=op_param.name,
                        reason=exceptions.IncorrectTypeReason(
                            actual=handler_param_type,
                            expected=[op_param_type],
                        ),
                    ), )
            else:
                key = model.OASParam(
                    param_in=oas.parameter_in(op_param),
                    param_name=op_param.name,
                )
                param_mapping[key] = handler_param_name
        except KeyError:
            errors.add(
                exceptions.Error(
                    param_name=op_param.name,
                    reason='missing',
                ), )

    return errors, param_mapping
Beispiel #3
0
def _oas_handler_analyzer(
    specifications: Mapping[Path, oas_model.OASSpecification],
    f_ctx: FunctionContext,
) -> Type:
    # TODO(kornicameister) make `OASSpectification.operations` a mapping
    # to allow easy access

    oas_handler = f_ctx.arg_types[0][0]

    assert isinstance(oas_handler, CallableType)
    assert oas_handler.definition

    f_name = oas_handler.name
    oas_operation = _get_oas_operation(
        oas_handler.definition.fullname,
        specifications,
    )

    if oas_operation is None:
        return errors.not_oas_handler(
            msg=f'{f_name} is not OAS operation',
            ctx=f_ctx,
            line_number=oas_handler.definition.line,
        )

    signature: Dict[str, Type] = {
        k: v
        for k, v in dict(zip(
            oas_handler.arg_names,
            oas_handler.arg_types,
        ), ).items() if k is not None
    }.copy()
    sorted(signature)

    for oas_param, f_param in map(
            lambda ofp: (ofp, handler_model.get_f_param(ofp.name)),
            oas.operation_filter_parameters(
                oas_operation,
                'path',
                'query',
            ),
    ):
        handler_arg_type: Optional[Type] = signature.pop(f_param, None)
        handler_arg_default_value = _get_default_value(f_param, oas_handler)
        oas_default_values = oas.parameter_default_values(oas_param)

        if handler_arg_type is None:
            # log the fact that argument is not there
            _oas_handler_msg(
                f_ctx.api.msg.fail,
                f_ctx,
                (
                    errors.ERROR_INVALID_OAS_ARG,
                    (f'{f_name} does not declare OAS {oas.parameter_in(oas_param)} '
                     f'{oas_param.name}::{f_param} argument'),
                ),
            )
            continue

        oas_param_type = transform_parameter_to_type(
            oas_param,
            get_proper_type(handler_arg_type),
            handler_arg_default_value,
            f_ctx,
        )
        if not any((
                is_same_type(handler_arg_type, oas_param_type),
                is_equivalent_type(handler_arg_type, oas_param_type),
        )):
            errors.invalid_argument(
                msg=(f'[{f_name}({f_param} -> {oas_param.name})] '
                     f'expected {format_type(oas_param_type)}, '
                     f'but got {format_type(handler_arg_type)}'),
                ctx=f_ctx,
                line_number=handler_arg_type.line,
            )
            continue

        # validate default value
        if handler_arg_default_value is not _ARG_NO_DEFAULT_VALUE_MARKER:
            if len(oas_default_values):
                default_matches = handler_arg_default_value in oas_default_values
                if not default_matches:
                    errors.invalid_default_value(
                        msg=
                        (f'[{f_name}({f_param} -> {oas_param.name})] '
                         f'Incorrect default value. '
                         f'Expected {",".join(map(str, oas_default_values))} '
                         f'but got {handler_arg_default_value}'),
                        ctx=f_ctx,
                        line_number=handler_arg_type.line,
                    )
                    continue
            elif not isinstance(handler_arg_default_value, type(None)):
                errors.default_value_not_in_oas(
                    msg=
                    (f'[{f_name}({f_param} -> {oas_param.name})] '
                     f'OAS does not define a default value. '
                     f'If you want "{handler_arg_default_value}" to be '
                     f'consistent default value, it should be declared in OAS too. '
                     ),
                    ctx=f_ctx,
                    line_number=handler_arg_type.line,
                )
        elif oas_default_values:
            errors.invalid_default_value(
                msg=
                (f'[{f_name}({f_param} -> {oas_param.name})] OAS '
                 f'defines "{",".join(map(str, oas_default_values))}" as a '
                 f'default value. It should be reflected in argument default value.'
                 ),
                ctx=f_ctx,
                line_number=handler_arg_type.line,
            )
            continue

    if signature:
        # unconsumed handler arguments
        ...

    return f_ctx.default_return_type
Beispiel #4
0
def _analyze_headers_signature_set_oas_set(
    parameters: t.Sequence[oas.OASParameter],
    headers_arg: t.Any,
) -> t.Tuple[t.Set[exceptions.Error], model.ParamMapping]:
    logger.debug('"headers" found both in signature and operation')

    errors: t.Set[exceptions.Error] = set()
    param_mapping: t.Dict[model.OASParam, model.FunctionArgName] = {}

    param_headers = {model.get_f_param(rh.name): str(rh.name) for rh in parameters}
    all_headers_names = {
        **param_headers,
        **RESERVED_HEADERS,
    }

    try:
        r_headers_arg = types.resolve_root_type(headers_arg)
        entries = t.get_type_hints(r_headers_arg).items()

        logger.debug(
            'headers_arg is {h}:{t} with entries {e}',
            h=r_headers_arg,
            t=type(r_headers_arg),
            e=entries,
        )

        for hdr_param_name, hdr_param_type in entries:
            if hdr_param_name in all_headers_names:
                # now tricky part, for reserved headers we enforce str
                # for oas headers we do type check
                if hdr_param_name in RESERVED_HEADERS and hdr_param_type != str:
                    errors.add(
                        exceptions.Error(
                            param_name=f'headers.{hdr_param_name}',
                            reason=exceptions.IncorrectTypeReason(
                                actual=hdr_param_type,
                                expected=[str],
                            ),
                        ),
                    )
                    continue
                elif hdr_param_name in param_headers:
                    oas_param = next(
                        filter(
                            lambda p: p.name == param_headers[model.get_f_param(
                                hdr_param_name,
                            )],
                            parameters,
                        ),
                    )
                    oas_param_type = model.convert_oas_param_to_ptype(oas_param)
                    if oas_param_type != hdr_param_type:
                        errors.add(
                            exceptions.Error(
                                param_name=f'headers.{hdr_param_name}',
                                reason=exceptions.IncorrectTypeReason(
                                    actual=hdr_param_type,
                                    expected=[str],
                                ),
                            ),
                        )
                        continue

                param_mapping[model.OASParam(
                    param_in='header',
                    param_name=all_headers_names[model.get_f_param(
                        hdr_param_name,
                    )].lower(),
                )] = model.get_f_param(hdr_param_name)

            else:
                errors.add(
                    exceptions.Error(
                        param_name=f'headers.{hdr_param_name}',
                        reason='unknown',
                    ),
                )
    except TypeError:
        for hdr_param_name, hdr_param_type in all_headers_names.items():
            param_mapping[model.OASParam(
                param_in='header',
                param_name=hdr_param_type.lower(),
            )] = hdr_param_name

    return errors, param_mapping
Beispiel #5
0
import typing as t

from loguru import logger

from axion import oas
from axion.handler import exceptions
from axion.handler import model
from axion.utils import types

RESERVED_HEADERS: t.Mapping[model.FunctionArgName, str] = {
    model.get_f_param(rh): rh.lower()
    for rh in oas.OASReservedHeaders
}


def analyze(
    parameters: t.Sequence[oas.OASParameter],
    headers_arg: t.Optional[t.Type[t.Any]],
) -> t.Tuple[t.Set[exceptions.Error], model.ParamMapping]:
    """Analyzes signature of the handler against the headers.

    axion supports defining headers in signature using:
    - typing_extensions.TypedDict
    - typing.Mapping
    - typing.Dict
    - Any other type is rejected with appropriate error.

    Also, when parsing the signature along with operation, following is taken
    into account:
    1. function does not have "headers" argument and there are no custom OAS headers
        - OK
Beispiel #6
0
def _signature_set_oas_set(
    parameters: t.Sequence[oas.OASParameter],
    cookies_arg: t.Any,
) -> t.Tuple[t.Set[exceptions.Error], model.ParamMapping]:
    logger.debug('"cookies" found both in signature and operation')

    errors: t.Set[exceptions.Error] = set()

    param_mapping: t.Dict[model.OASParam, model.FunctionArgName] = {}
    param_cookies = {model.get_f_param(rh.name): rh.name for rh in parameters}

    try:
        r_cookies_arg = types.resolve_root_type(cookies_arg)
        entries = t.get_type_hints(r_cookies_arg).items()

        logger.debug(
            'cookies_arg is {h}:{t} with entries {e}',
            h=r_cookies_arg,
            t=type(r_cookies_arg),
            e=entries,
        )

        for cookie_param_name, cookie_param_type in entries:
            if cookie_param_name in param_cookies:

                oas_param = next(
                    filter(
                        lambda p: p.name == param_cookies[model.get_f_param(
                            cookie_param_name,
                        )],
                        parameters,
                    ),
                )
                oas_param_type = model.convert_oas_param_to_ptype(oas_param)
                if oas_param_type != cookie_param_type:
                    errors.add(
                        exceptions.Error(
                            param_name=f'cookies.{cookie_param_name}',
                            reason=exceptions.IncorrectTypeReason(
                                actual=cookie_param_type,
                                expected=[oas_param_type],
                            ),
                        ),
                    )
                else:
                    param_mapping[model.OASParam(
                        param_in='cookie',
                        param_name=param_cookies[model.get_f_param(
                            cookie_param_name,
                        )],
                    )] = model.get_f_param(cookie_param_name)

            else:
                errors.add(
                    exceptions.Error(
                        param_name=f'cookies.{cookie_param_name}',
                        reason='unknown',
                    ),
                )
    except TypeError:
        for hdr_param_name, hdr_param_type in param_cookies.items():
            param_mapping[model.OASParam(
                param_in='cookie',
                param_name=hdr_param_type,
            )] = hdr_param_name

    return errors, param_mapping