RustとLambdaでClovaスキル作る　前編

前回、google homeアプリ作りました。
[RustとLambdaでなんか作る 後編 （リクガメの食べ物しらべるghomeアプリ作った)](https://qiita.com/ikegam1/items/760b79d03cf9059cd73a)

今回も手段が目的です。
ちょっと難易度上げてClovaスキルに同じやつを作ってみましょう。
Dialogflowがない分、作りこみが大変です。
汎用性もちょっと考えつつ、少し時間をかけてつくる。

## 下調べ１　clovaのreqestとレスポンス

他に公開してるクイズがあるのでそれのテスト画面でチェックしてみました。

### Request

``` json:例
{
    "version": "1.0",
    "session": {
        "sessionId": "37564251-297a-4961-8f02-xxxxxxxx",
        "sessionAttributes": {},
        "user": {
            "userId": "xxxxxxxx-K-fZUQ",
            "accessToken": "ead11e3a-a7c6-44de-a2fb-xxxxxxx"
        },
        "new": true
    },
    "context": {
        "System": {
            "application": {
                "applicationId": "tokyo.ikegami.xxxxx"
            },
            "user": {
                "userId": "xxxxxxxx-K-fZUQ",
                "accessToken": "ead11e3a-a7c6-44de-a2fb-xxxxxxxx"
            },
            "device": {
                "deviceId": "b323e077-56ef-4c83-98ee-xxxxxxxx",
                "display": {
                    "size": "l100",
                    "orientation": "landscape",
                    "dpi": 96,
                    "contentLayer": {
                        "width": 640,
                        "height": 360
                    }
                }
            }
        }
    },
    "request": {
        "type": "IntentRequest",
        "intent": {
            "name": "AnswerIntent",
            "slots": {
                "answer": {
                    "name": "answer",
                    "value": "キッシュ"
                }
            }
        }
    }
}
```

### response

``` json:例１
{
    "response": {
        "card": {},
        "directives": [],
        "outputSpeech": {
            "type": "SimpleSpeech",
            "values": {
                "lang": "ja",
                "type": "PlainText",
                "value": "キッシュって10回言ってみて！"
            }
        },
        "reprompt": {
            "outputSpeech": {
                "type": "SimpleSpeech",
                "values": {
                    "lang": "ja",
                    "type": "PlainText",
                    "value": "キッシュだよ。10回ね"
                }
            }
        },
        "shouldEndSession": false
    },
    "sessionAttributes": {
        "cnta": 0,
        "cntq": 0,
        "history": [],
        "quiz": {
            "a": [
                "タルト",
                "たると",
                "tart"
            ],
            "chanq": "キッシュ",
            "i": "\n繰り返しワード：キッシュ\nキッシュも美味しいけどキッシュはお菓子じゃないよね。いちごタルトが王道？\n考案者：ゆみちゃん",
            "key": 13,
            "q": "パートシュクレという生地で作った器の上に、果物などを盛り付けたお菓子はなーんだ？"
        }
    },
    "version": "1.0"
}
```

``` json:例２
{
    "response": {
        "card": {},
        "directives": [],
        "outputSpeech": {
            "type": "SimpleSpeech",
            "values": {
                "lang": "ja",
                "type": "PlainText",
                "value": "遊んでくれてありがとう。またやってね！"
            }
        },
        "shouldEndSession": true
    },
    "sessionAttributes": {},
    "version": "1.0"
}
```
## 下調べ２ Rustとjson

さて、もうちょっといいjsonの扱い方はないもんか。
前回みたいに階層ごとにstruct作ってたらめっちゃややこい事になる。
でもまあ、[serde-rs/json](https://github.com/serde-rs/json)みてたら事前に定義してなくてもなんとかなるっぽかった。

``` Rust:main.rs

use std::error::Error;

use lambda_http::{lambda, Request, Response, Body};
use lambda_runtime::{error::HandlerError, Context};
use log::{self, info};
use simple_logger;

extern crate serde_derive;
extern crate serde;
extern crate serde_json;

fn main() -> Result<(), Box<dyn Error>> {
    simple_logger::init_with_level(log::Level::Info).unwrap();
    lambda!(handler);

    Ok(())
}

fn handler(e: Request, _c: Context) -> Result<Response<Body>, HandlerError> {
  let v: serde_json::Value = serde_json::from_slice(e.body().as_ref()).unwrap();
  info!("{:#?}", v);
  Ok(
    Response::builder()
     .status(200)
     .header("Content-Type", "application/json; charset=UTF-8")
     .body(
        format!("{}", serde_json::json!({
          "response": {
              "card": {},
              "directives": [],
              "outputSpeech": {
                  "type": "SimpleSpeech",
                  "values": {
                      "lang": "ja",
                      "type": "PlainText",
                      "value": v["request"]["type"]
                  }
              },
              "shouldEndSession": true
          },
          "sessionAttributes": {},
          "version": "1.0"
        })).into(),
      )
     .expect("none")
  )
}
```

作成したら npx serverless deployします。
サーバーレスのテンプレは前回と同じく、https://github.com/softprops/serverless-aws-rustです。

## clovaスキル作ってみる

1. https://clova-developers.line.biz/ を開き、ログインします。
1. スキル設定を開き、新規チャネル作成（プロバイダがない場合はそれも）
2. チャネル名を「リクガメの食べ物」としました。
3. 基本情報とか入力
4. webhookのURLは作成したlambdaのURLに
5. で、対話モデルは下記の感じでとりあえず。

![65.png](https://qiita-image-store.s3.amazonaws.com/0/308709/66328020-fde6-3788-ee24-b38e64716023.png)

foodSlotというのを作って、それには```小松菜```みたいなフード名を入れる想定、
それをaskIntentで拾う想定です。
設定したら一回ビルドしておきます。

で、テスト画面で試すと・・

![66.png](https://qiita-image-store.s3.amazonaws.com/0/308709/31dd812c-38dd-c481-fe85-a8fcffe4d6e6.png)

ちゃんと返ってきました。
お、意外とスムーズか？

## よし、一回リリースしておこう。

なんか意外といけるかもしれないので、だいたい作って申請してしまいました。
ちょっとシンプルすぎてどうかなとは思うけど。
ソースは雑なので次の後編で申請結果と共にさらしますよ。
ちなみにjsonのStringにダブルクォートがついたままになってて、その状態で無理やり比較している苦しい状況。。来週まで時間取れそうにないんだけど、来週にちゃんと整えよう。

というわけで前編はここまで。