【LINE API】Messaging APIのメッセージオブジェクトを検証するAPIを試してみた

もう2ヶ月前になりますが、LINE DevelopersからMessaging APIのメッセージオブジェクトを検証するAPIが追加されたと発表がありました。
[ニュース：Messaging APIのメッセージオブジェクトを検証するエンドポイントを追加しました | LINE Developers](https://developers.line.biz/ja/news/2022/10/24/validate-message-objects-api/)

私としては結構待ち望んでいた機能でもあり、今まで自前でバリデーションを行っていたところを早くこちらに置き換えたいなと思っています。

今回は、こちらのAPIを実際に試してみます。

## 準備
- 以下の記事を参考にMessaging APIのチャンネルを作成してください。

https://zenn.dev/tokku5552/articles/create-line-provider

- `Messaging API`タブの最下部にある`チャネルアクセストークン`で発行ボタンをおして、チャネルアクセストークンを発行しておきます。
![スクリーンショット 2022-12-17 22.12.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141823/b975cbf1-9d1c-1aaf-d45a-7a82ddab570c.png)


- 環境変数にChannel Access Tokenをセットしておきます。

```bash:
export CHANNEL_ACCESS_TOKEN={channel access token}
```

## 実際に試す

まずは応答メッセージで試してみます。
 
https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-reply-message

```bash:
% curl -v -X POST https://api.line.me/v2/bot/message/validate/reply \
-H "Authorization: Bearer ${CHANNEL_ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
-d \
'{
  "messages": [
    {
      "type": "text",
      "text": "Hello, world"
    }
  ]
}'

# 結果) 一部マスキングしています

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 147.92.144.180:443...
* Connected to api.line.me (147.92.144.180) port 443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
*  subject: C=JP; ST=Tokyo-to; L=Shinjuku-ku; O=LINE Corporation; CN=*.line.me
*  start date: Aug  8 08:36:05 2022 GMT
*  expire date: Sep  9 08:36:04 2023 GMT
*  subjectAltName: host "api.line.me" matched cert's "*.line.me"
*  issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign RSA OV SSL CA 2018
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: POST]
* h2h3 [:path: /v2/bot/message/validate/reply]
* h2h3 [:scheme: https]
* h2h3 [:authority: api.line.me]
* h2h3 [user-agent: curl/7.84.0]
* h2h3 [accept: */*]
* h2h3 [authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]
* h2h3 [content-type: application/json]
* h2h3 [content-length: 86]
* Using Stream ID: 1 (easy handle 0x7f7982011a00)
> POST /v2/bot/message/validate/reply HTTP/2
> Host: api.line.me
> user-agent: curl/7.84.0
> accept: */*
> authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
> content-type: application/json
> content-length: 86
>
* We are completely uploaded and fine
* Connection state changed (MAX_CONCURRENT_STREAMS == 20)!
< HTTP/2 200
< x-xss-protection: 1; mode=block
< x-line-request-id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
< x-frame-options: DENY
< x-content-type-options: nosniff
< server: envoy
< pragma: no-cache
< expires: 0
< date: Sat, 17 Dec 2022 13:00:37 GMT
< content-type: application/json
< content-length: 2
< cache-control: no-cache, no-store, max-age=0, must-revalidate
<
* Connection #0 to host api.line.me left intact
{}%
```

無事ステータスコードが200で返ってきました。

次に`type`を`text1`にしてみます。(結果は`jq`で整形しています。)
```bash:
% curl -X POST https://api.line.me/v2/bot/message/validate/reply \
-H "Authorization: Bearer ${CHANNEL_ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
-d \
'{
  "messages": [
    {
      "type": "text1",
      "text": "Hello, world"
    }
  ]
}' | jq

# 結果)
{
  "message": "The request body has 1 error(s)",
  "details": [
    {
      "message": "Must be one of the following values: [text, image, video, audio, location, sticker, template, imagemap, flex]",
      "property": "messages[0].type"
    }
  ]
}
```

ちゃんと`details`の`message`の中で理由を返してくれています。

試しに存在しない`emoji id`を送ってみます。
```bash:
% curl -X POST https://api.line.me/v2/bot/message/validate/reply \
-H "Authorization: Bearer ${CHANNEL_ACCESS_TOKEN}" \
-H 'Content-Type: application/json' \
-d \
'{
  "messages": [
    {
      "type": "text",
      "text": "$ LINE emoji $",
      "emojis": [
        {
          "index": 0,
          "productId": "5ac1bfd5040ab15980c9b435",
          "emojiId": "001"
        },
        {
          "index": 13,
          "productId": "5ac1bfd5040ab15980c9b435",
          "emojiId": "999"
        }
      ]
    }
  ]
}' | jq

# 結果
{
  "message": "A message (messages[0]) in the request body is invalid",
  "details": [
    {
      "message": "Invalid product id or emoji id",
      "property": "emojis"
    }
  ]
}
```

「`product id`か`emoji id` が間違ってるよ」とちゃんと教えてくれました:tada: 便利！


他には一応以下のメッセージの検証に対応しているようですが、すべて`messages`プロパティ以外のプロパティの値は検証してくれないようなので、あまり使い分けるイメージがわきませんでした。:thinking:
(※どのメッセージ種別もリクエストに含めることができる`messages`オブジェクトの最大件数は5件で、メッセージオブジェクト自体は共通仕様のため。)
|種別|エンドポイント|
|--|--|
|[応答メッセージ](https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-reply-message)|`POST https://api.line.me/v2/bot/message/validate/reply`|
|[プッシュメッセージ](https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-push-message)|`POST https://api.line.me/v2/bot/message/validate/push`|
|[マルチキャストメッセージ](https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-multicast-message)|`POST https://api.line.me/v2/bot/message/validate/multicast`|
|[ナローキャストメッセージ](https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-narrowcast-message)|`POST https://api.line.me/v2/bot/message/validate/narrowcast`|
|[ブロードキャストメッセージ](https://developers.line.biz/ja/reference/messaging-api/#validate-message-objects-of-broadcast-message)|`POST https://api.line.me/v2/bot/message/validate/broadcast`|

## TypeScriptでの利用例
せっかくなので、TypeScriptで利用する例を書いてみました。
`ts-node`で動くようにしているので、実際に使うときは必要な部分だけパクっていただければと思います。
```ts:src/main.ts
import axios from "axios";

// 予め`CHANNEL_ACCESS_TOKEN`という環境変数にチャネルアクセストークンをセットしておく
const CHANNEL_ACCESS_TOKEN = process.env.CHANNEL_ACCESS_TOKEN || "";

// エラーのときの型(detailsの中身)
interface ValidateErrorDetail {
  message: string;
  property: string;
}
// エラーのときの型
interface ValidateError {
  message: string;
  details: ValidateErrorDetail[];
}

const validater = async (body: string) => {
  const headers = {
    "Content-Type": "application/json",
    Authorization: `Bearer ${CHANNEL_ACCESS_TOKEN}`,
  };
  await axios
    .post("https://api.line.me/v2/bot/message/validate/reply", body, {
      headers: headers,
    })
    .then((_) => {
      // 成功した場合は何もしない
    })
    .catch((reason) => {
      const validateError: ValidateError = reason.response.data;
      console.error(validateError);
      throw new Error(validateError.message);
    });
  console.info("Success Validate!");
};

const body = {
  messages: [
    {
      type: "text",
      text: "Hello, world",
    },
  ],
};

validater(JSON.stringify(body));
```
実際にts-nodeで動かしてみます。

まずはそのまま
```bash:
% npx ts-node src/main.ts
Success Validate!
```

次は`type`を`text1`にして実行
```bash:
% npx ts-node src/main.ts
{
  message: 'The request body has 1 error(s)',
  details: [
    {
      message: 'Must be one of the following values: [text, image, video, audio, location, sticker, template, imagemap, flex]',
      property: 'messages[0].type'
    }
  ]
}
Error: The request body has 1 error(s)
    at /path/to/src/main.ts:28:13
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
```

こちらも無事使えそうです :tada: 

## まとめ
使い分け方は思いつかないものの、単純に各メッセージ送信前にこちらで予めバリデーションしておくと良さそうだなと思いました:eyes: