def add(self, rule, method, name, callback):
        """ Add a new rule or replace the target for an existing rule.

        >>> from kobin import Response
        >>> r = Router()
        >>> def view(user_id: int) -> Response:
        ...     return Response(f'You are {user_id}')
        ...
        >>> r.add('/users/{user_id}', 'GET', 'user-detail', view)
        >>> path, name, methods = r.endpoints[0]
        >>> path
        '/users/{user_id}'
        >>> name
        'user-detail'
        >>> callback, type_hints = methods['GET']
        >>> view == callback
        True
        >>> type_hints['user_id'] == int
        True
        """
        if rule != '/':
            rule = rule.rstrip('/')
        method = method.upper()

        for i, e in enumerate(self.endpoints):
            r, n, callbacks = e
            if r == rule:
                assert name == n and n is not None, (
                    "A same path should set a same name for reverse routing."
                )
                callbacks[method] = (callback, get_type_hints(callback))
                self.endpoints[i] = (r, name, callbacks)
                break
        else:
            e = (rule, name, {method: (callback, get_type_hints(callback))})
            self.endpoints.append(e)