RustとLambdaでClovaスキル作る  後編(crates.ioに上げてみる)

[前編](https://qiita.com/ikegam1/items/2c5b629df2fbd5764ab1)

そんなわけでClovaストアで**リクガメの食べ物**公開されました。
呼び出し名とかで申請戻しとかはありましたが。

しかし、手段が目的なのでまだまだ続きます。
公開したもののソースをいじります。

## パッケージにして公開してみたい

https://crates.io/
ですかね？
clovaでサーチしてみると、```0 crates found. Get started and create your own.```
まだ被りはないとみていいかな。

公開方法はこのあたり？
[Publishing on crates.io](https://doc.rust-lang.org/cargo/reference/publishing.html)
手順に沿って進められるとこまで進めておく

1. githubアカウントでログインして連携
2. cargo.ioのsetting画面でAPIを有効にする
![67.png](https://qiita-image-store.s3.amazonaws.com/0/308709/7be2d29e-2fa5-e1bd-d65f-c41681fb52a2.png)

3. ```save it to ~/.cargo/credentials```する
4. パッケージ名どうしようかな。とりあえず```cargo clova-webhook-service```としとこう

と、やってみたら怒られる

```console
$ cargo clova-webhook-service
error: could not parse TOML configuration in `/home/ec2-user/.cargo/credentials`

Caused by:
  could not parse input as TOML

Caused by:
  expected an equals, found an identifier at line 1
```

TOML形式で書かないとダメでしたか。
どうやら```token = "[api-token]"```って書くらしい。

5. で、githubにソースがないといけないみたいなのでレポジトリを作って空コミットしてみました。
6. そして```cargo package```

```console
$ cargo package
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See http://doc.crates.io/manifest.html#package-metadata for more info.
   Packaging clova-webhook-service v0.1.0 (/home/ec2-user/ws/clova-webhook-service)
   Verifying clova-webhook-service v0.1.0 (/home/ec2-user/ws/clova-webhook-service)
   Compiling clova-webhook-service v0.1.0 (/home/ec2-user/ws/clova-webhook-service/target/package/clova-webhook-service-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 7.49s
```

7. したら```./target/package/clova-webhook-service-0.1.0.crate```ってファイルができてた。

あとはちゃんと作ってcargo publishってやればいいんだろうか。

### そんなわけで作ります。

なんか見本にしていこうと思いますが、
https://docs.rs/lambda_http/0.1.0/lambda_http/
とか
https://github.com/softprops/commodore
とかをなんとなく見てみよう。

動作としては、受け取ったリクエストを渡すとリクエスト内容とメソッド入りのstructにして返す感じにしたい。
こんな感じでやるといいのかな。ちょっとメモ↓
https://doc.rust-jp.rs/the-rust-programming-language-ja/1.6/book/traits.html
Struct定義してtrait被せてimplementでgetterとsetter用意してやればいい？

あと、[serde-json](https://github.com/serde-rs/json)。これが肝です。
Deserializeだとかserde(default)みたいなやつをちゃんと理解しないと。
こちらがわかりやすい。
[RustのSerdeの簡単な紹介](https://qiita.com/garkimasera/items/0442ee896403c6b78fb2)
あとこれも。
https://docs.serde.rs/serde_json/value/enum.Value.html

### 要件

- requestをobject化できる
- requestからrequest typeをgetできる
- requestからintent nameをgetできる
- requestからintent slotをgetできる
- requestからsession_attributesをgetできる
- responseを返せる
- responseにsimple speechのtextをsetできる
- responseにend sessionのtrue/falseをsetできる
- responseにrepromptをsetできる
- responseにsession_attributesをsetできる

### そんなわけで・・

コンパイラに5万回くらい怒られつつ、ひとまずのやつができました。
自分で使ってみたいのでcrates.ioにアップできないか試してみる

```
cargo package
cargo publish
```

お、上がったやん。あっさり上がってしまった。
[clova_webhook_utils 0.0.4](https://crates.io/crates/clova_webhook_utils)
(ちなみに最初clova-webhook-serviceって名前だったけど、ハイフンをアンスコにしたくて変えました)

作ったやつのレポジトリ。
https://github.com/ikegam1/clova_webhook_utils

**リクガメの食べ物スキル**もこれを使ったやつに変更しました。ソースはだいたい上記のREADME.mdに書かれてるやつです。こんな感じに。

``` rust:example
use std::error::Error;

use lambda_http::{lambda, Response, Request, Body};
use lambda_runtime::{error::HandlerError, Context};
use clova_webhook_utils::{RequestData, RequestDataStruct, ResponseData, ResponseDataStruct};

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

fn main() -> Result<(), Box<dyn Error>>{
    lambda!(handler);

    Ok(())
}

fn handler(e: Request, _c: Context) -> Result<Response<Body>, HandlerError> {
    let req: RequestDataStruct =  serde_json::from_slice(e.body().as_ref()).unwrap();
    let mut speech: String = "hello".to_string();
    let mut end: bool = true;
    match &*req.get_request_type() {
        "LaunchRequest" => {speech = "Hello".to_string(); end=false;},
        "SessionEndedRequest" => {speech = "bye".to_string();},
        _ => {}
    }
    match &*req.get_intent_name() {
        "HelloIntent" => { speech = "hello".to_string(); end=false; },
        "FinishIntent" => { speech = "bye".to_string(); },
        "Clova.GuideIntent" => {speech = "usage..".to_string(); end=false;},
        "Clova.YesIntent" => {speech = "ok.".to_string(); end=false;},
        "Clova.NoIntent" => {speech ="no.".to_string();},
        "Clova.CancelIntent" => {speech = "bye".to_string();},
        "askIntent" => {
            // String is quoted.. so remove it
            let food: String = &req.get_slots().unwrap().pointer("/foodSlot/value").unwrap().to_string().trim_matches('"').to_string();
            if food == "apple".to_string() {
                speech = "apple".to_string();
            }else{
                speech = "pineapple".to_string();
            }
        },
        _ => {}
    }

    let mut res: ResponseDataStruct =  ResponseDataStruct::new().unwrap();
    res.set_simple_speech_text(speech, end);
    res.set_session_attributes_from_str("{\"current\":\"hello\"}".to_string());
    Ok(
        Response::builder()
           .status(200)
           .header("Content-Type", "application/json; charset=UTF-8")
           .body(
               format!("{}",serde_json::json!(res)).into(),
           )
           .expect("none")
    )
}
```

基本、lambda経由で使う事しか想定してないです。
入ってきたRequestはそのままRequestDataStructに入れる

```rust
let req: RequestDataStruct =  serde_json::from_slice(e.body().as_ref()).unwrap();
```

レスポンスは

```rust
let mut res: ResponseDataStruct =  ResponseDataStruct::new().unwrap();
```

ってやって作る。
この二つをメソッド使って操作していく感じです。

メソッドたちはこんな感じ

```rust
pub trait RequestData {
    fn get_session(&self) -> Result<&serde_json::Value, String>;
    fn get_context(&self) -> Result<&serde_json::Value, String>;
    fn get_request(&self) -> Result<&serde_json::Value, String>;
    fn get_request_type(&self) -> String;
    fn get_intent_name(&self) -> String;
    fn get_slots(&self) -> Result<&serde_json::Value, String>;
    fn get_session_attributes(&self) -> Result<&serde_json::Value, String>;
}
pub trait ResponseData{
    fn simple_speech(&self) -> Result<serde_json::Value, Box<dyn Error>>;
    fn set_simple_speech_text(&mut self, s: String, e: bool);
    fn set_reprompt_simple_speech_text(&mut self, s: String);
    fn set_session_attributes_from_str(&mut self, s: String);
    fn set_session_attributes(&mut self, j: serde_json::Value);
    fn new() -> Result<ResponseDataStruct, Box<dyn Error>>;
}
```

戻り値がStringだらけだったり、lifetimeとかannotationとか使えてないあたりが素人感出てる。。
まだまだRustについてはわかっておらず、新卒が書いたレベルのソースかって感じですが、まあひとまずは満足。全世界に公開されちゃったもんな。
しょぼいので、当分の間は誰にも使われずひっそりしててほしい。。

しかし、Rustむずいすね。本でも買おうかなー。

なお、**リクガメの食べ物スキル**の実際のソースはこちら。
https://github.com/ikegam1/clova-lambdaonrust-kamefood