API/rules.js

import {
  emptyFunc,
  every,
  extend,
  isArray,
  isBlob,
  isBoolean,
  isFile,
  isFileList,
  isFunction,
  isInteger,
  isObject,
  isOneOf,
  isString,
  isURL,
  keys,
  passesOneOf,
} from '../util'
import { KakaoError } from '../common'
import { appKey } from './authType'

const postApiCommonParams = {
  permission: isOneOf(['A', 'F', 'M']),
  enable_share: isBoolean,
  android_exec_param: isString,
  ios_exec_param: isString,
  android_market_param: isString,
  ios_market_param: isString,
}

const secureResource = {
  secure_resource: isBoolean,
}

const forceSecureResource = settings => {
  if (settings.secure_resource === false) {
    if (console) {
      console.warn('KakaoWarning: The secure_resource parameter is deprecated.')
    }
    settings.secure_resource = true
  }
}

function storyActivityContentValidator(obj) {
  if (!isString(obj)) {
    return false
  }

  if (obj.length === 0 || obj.length > 2048) {
    throw new KakaoError('content length should be between 0 and 2048')
  }

  return true
}

function kageImageUrlListValidator(obj) {
  if (!isArray(obj)) {
    return false
  }

  return every(obj, path => {
    if (!isString(path)) {
      return false
    }

    if (isURL(path)) {
      throw new KakaoError(
        `url in image_url_list should be a kage url, obtained from '/v1/api/story/upload/multi'.`
      )
    }

    return true
  })
}

function hasHeaderBackgroundImage(obj) {
  if (obj.header_image_url || obj.header_image_width || obj.header_image_height) {
    delete obj.header_image_url
    delete obj.header_image_width
    delete obj.header_image_height

    if (console) {
      const params = ['header_image_url', 'header_image_width', 'header_image_height']
      console.warn(`KakaoWarning: The parameters (${params.join(', ')}) for header background image are deprecated.`)
    }
  }

  return true
}

const apiRules = {
  /**
   * USER MANAGEMENT
   */
  '/v1/user/signup': {
    method: 'post',
    data: {
      optional: {
        properties: isObject,
      },
    },
  },
  '/v1/user/unlink': {
    method: 'post',
  },
  '/v2/user/me': {
    method: 'get',
    data: {
      optional: extend(
        {
          property_keys: isArray,
        },
        secureResource,
      ),
    },
  },
  '/v1/user/logout': {
    method: 'post',
    data: {},
  },
  '/v1/user/update_profile': {
    method: 'post',
    data: {
      required: { properties: isObject }
    }
  },

  /**
   * KAKAO TALK
   */
  '/v1/api/talk/profile': {
    method: 'get',
    data: {
      optional: secureResource,
      after: forceSecureResource,
    },
  },
  '/v2/api/talk/memo/send': {
    method: 'post',
    data: {
      required: {
        template_id: isInteger,
      },
      optional: {
        template_args: isObject,
      },
    },
  },
  '/v2/api/talk/memo/scrap/send': {
    method: 'post',
    data: {
      required: {
        request_url: isString,
      },
      optional: {
        template_id: isInteger,
        template_args: isObject,
      },
    },
  },
  '/v2/api/talk/memo/default/send': {
    method: 'post',
    data: {
      required: {
        template_object(obj) {
          return isObject(obj) && hasHeaderBackgroundImage(obj)
        },
      },
    },
  },

  /**
   * KAKAO STORY
   */
  '/v1/api/story/profile': {
    method: 'get',
    data: {
      optional: secureResource,
    },
  },
  '/v1/api/story/isstoryuser': {
    method: 'get',
  },
  '/v1/api/story/mystory': {
    method: 'get',
    data: {
      required: {
        id: isString,
      },
    },
  },
  '/v1/api/story/mystories': {
    method: 'get',
    data: {
      optional: {
        last_id: isString,
      },
    },
  },
  '/v1/api/story/linkinfo': {
    method: 'get',
    data: {
      required: {
        url: isString,
      },
    },
  },
  '/v1/api/story/post/note': {
    method: 'post',
    data: {
      required: {
        content: storyActivityContentValidator,
      },
      optional: postApiCommonParams,
    },
  },
  '/v1/api/story/post/photo': {
    method: 'post',
    data: {
      required: {
        image_url_list: kageImageUrlListValidator,
      },
      optional: extend(
        {
          content: storyActivityContentValidator
        },
        postApiCommonParams,
      ),
    },
  },
  '/v1/api/story/post/link': {
    method: 'post',
    data: {
      required: {
        link_info: isObject,
      },
      optional: extend(
        {
          content: storyActivityContentValidator,
        },
        postApiCommonParams,
      ),
    },
  },
  '/v1/api/story/upload/multi': {
    method: 'post',
    data: {},
  },
  '/v1/api/story/delete/mystory': {
    method: 'delete',
    data: {
      required: {
        id: isString,
      },
    },
  },

  /**
   * KAKAO LINK
   */
  '/v2/api/kakaolink/talk/template/validate': {
    method: 'get',
    data: {
      required: {
        link_ver: isString,
        template_id: isInteger,
      },
      optional: {
        template_args: isObject,
      },
    },
    authType: appKey,
  },
  '/v2/api/kakaolink/talk/template/scrap': {
    method: 'get',
    data: {
      required: {
        link_ver: isString,
        request_url: isString,
      },
      optional: {
        template_id: isInteger,
        template_args: isObject,
      },
    },
    authType: appKey,
  },
  '/v2/api/kakaolink/talk/template/default': {
    method: 'get',
    data: {
      required: {
        link_ver: isString,
        template_object: isObject,
      },
    },
    authType: appKey,
  },
  '/v1/user/access_token_info': {
    method: 'get',
    data: {},
  },
  '/v2/api/talk/message/image/upload': {
    method: 'post',
    data: {
      required: {
        file: isObject,
      },
    },
    authType: appKey,
  },
  '/v2/api/talk/message/image/delete': {
    method: 'delete',
    data: {
      required: {
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v2/api/talk/message/image/scrap': {
    method: 'post',
    data: {
      required: {
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v1/vision/face/detect': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
        image_url: isString,
        threshold: isInteger,
      },
    },
    authType: appKey,
  },
  '/v1/vision/product/detect': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
        image_url: isString,
        threshold: isInteger,
      },
    },
    authType: appKey,
  },
  '/v1/vision/thumbnail/crop': {
    method: 'post',
    data: {
      required: {
        width: isInteger,
        height: isInteger,
      },
      optional: {
        file: isObject,
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v1/vision/thumbnail/detect': {
    method: 'post',
    data: {
      required: {
        width: isInteger,
        height: isInteger,
      },
      optional: {
        file: isObject,
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v1/vision/multitag/generate': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v1/vision/adult/detect': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
        image_url: isString,
      },
    },
    authType: appKey,
  },
  '/v1/vision/text/detect': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
      },
    },
    authType: appKey,
  },
  '/v1/vision/text/recognize': {
    method: 'post',
    data: {
      optional: {
        file: isObject,
        boxes: passesOneOf([isString, isArray]),
      },
    },
    authType: appKey,
  },
  '/v1/translation/translate': {
    method: 'post',
    data: {
      required: {
        query: isString,
        src_lang: isString,
        target_lang: isString,
      },
    },
    authType: appKey,
  },
  '/v1/user/service/terms': {
    method: 'get',
  },
  '/v1/user/shipping_address': {
    method: 'get',
    data: {
      optional: {
        address_id: isInteger,
        from_updated_at: isInteger,
        page_size: isInteger,
      },
    },
  },
  '/v1/api/talk/plusfriends': {
    method: 'get',
    data: {
      optional: {
        plus_friend_public_ids: isArray,
      },
    },
  },
  '/v1/friends': {
    method: 'get',
    data: {
      optional: extend(
        {
          offset: isInteger,
          limit: isInteger,
          order: isString,
          friend_order: isString,
        },
        secureResource,
      ),
      after: forceSecureResource,
    },
  },
  '/v1/api/talk/friends': {
    method: 'get',
    data: {
      optional: extend(
        {
          offset: isInteger,
          limit: isInteger,
          order: isString,
        },
        secureResource,
      ),
      after: forceSecureResource,
    },
  },
  '/v1/api/talk/friends/message/send': {
    method: 'post',
    data: {
      required: {
        template_id: isInteger,
        receiver_uuids: isArray,
        receiver_id_type: isString,
      },
      optional: {
        template_args: isObject,
      },
      defaults() {
        return {
          receiver_id_type: 'uuid',
        }
      },
    },
  },
  '/v1/api/talk/friends/message/scrap/send': {
    method: 'post',
    data: {
      required: {
        request_url: isString,
        receiver_uuids: isArray,
        receiver_id_type: isString,
      },
      optional: {
        template_id: isInteger,
        template_args: isObject,
      },
      defaults() {
        return {
          receiver_id_type: 'uuid',
        }
      },
    },
  },
  '/v1/api/talk/friends/message/default/send': {
    method: 'post',
    data: {
      required: {
        template_object(obj) {
          return isObject(obj) && hasHeaderBackgroundImage(obj)
        },
        receiver_uuids: isArray,
        receiver_id_type: isString,
      },
      defaults() {
        return {
          receiver_id_type: 'uuid',
        }
      },
    },
  },
}

export default {
  request: {
    required: {
      url(url) {
        return isOneOf(keys(apiRules))(url)
      },
    },
    optional: {
      data: isObject,
      files(obj) {
        return passesOneOf([isArray, isFileList])(obj) &&
          every(obj, passesOneOf([isFile, isBlob]))
      },
      file: isFile,
      success: isFunction,
      fail: isFunction,
      always: isFunction,
    },
    defaults: {
      data: {},
      success: emptyFunc,
      fail: emptyFunc,
      always: emptyFunc,
    },
  },
  api: apiRules,
}