RustとLambdaでなんか作る 中編

[前回](https://qiita.com/ikegam1/items/d4b56756557dbc86d97d)の続きです。

Rustがlambdaで動いてapi gateway経由でHello Worldするとこまでやりました。
本日は、ひとまずhttps://github.com/awslabs/aws-lambda-rust-runtime/tree/master/lambda-http/examples に乗ってるbasic.rsを見てみる。
まだなんとなくわかる程度です。

## basic.rsをdeployしてみる

``` Rust:basic.rs
use std::error::Error;

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

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

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<impl IntoResponse, HandlerError> {
    Ok(match e.query_string_parameters().get("first_name") {
        Some(first_name) => format!("Hello, {}!", first_name).into_response(),
        _ => {
            error!("Empty first name in request {}", c.aws_request_id);
            Response::builder()
                .status(400)
                .body("Empty first name".into())
                .expect("failed to render response")
        }
    })
}
```

Cargo.tomlに```log = "^0.4"```と```simple_logger = "^1"```を追記してます。

実行してみると・・

```
$ curl -XGET  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?aaa"
Empty first name
$ curl -XGET  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob"
Hello, bob!
```

なるほどー。
lambda_httpのrequest.rsみると・・

```
/// Internal representation of an Lambda http event from both
/// both ALB and API Gateway proxy event perspectives
#[doc(hidden)]
#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LambdaRequest<'a> {
    pub(crate) path: Cow<'a, str>,
    #[serde(deserialize_with = "deserialize_method")]
    pub(crate) http_method: Method,
    #[serde(deserialize_with = "deserialize_headers")]
    pub(crate) headers: HeaderMap<HeaderValue>,
    /// For alb events these are only present when
    /// the `lambda.multi_value_headers.enabled` target group setting turned on
    #[serde(default, deserialize_with = "deserialize_multi_value_headers")]
    pub(crate) multi_value_headers: HeaderMap<HeaderValue>,
    #[serde(deserialize_with = "nullable_default")]
    pub(crate) query_string_parameters: StrMap,
    /// For alb events these are only present when
    /// the `lambda.multi_value_headers.enabled` target group setting turned on
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) multi_value_query_string_parameters: StrMap,
    /// ALB events do not have path parameters.
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) path_parameters: StrMap,
    /// ALB events do not have stage variables.
    #[serde(default, deserialize_with = "nullable_default")]
    pub(crate) stage_variables: StrMap,
    pub(crate) body: Option<Cow<'a, str>>,
    #[serde(default)]
    pub(crate) is_base64_encoded: bool,
    pub(crate) request_context: RequestContext,
}
```

って書いてあったりするから、たぶん```e```のオブジェクトからはbodyもとれる。たぶん。

## いじる

少しいじって動作検証です。
まずPOSTにapi gateway対応するようにしてjson投げてみました。

```
curl -XPOST  "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob" -d @package.json
Hello, bob!
```

で、https://github.com/awslabs/aws-lambda-rust-runtime/blob/master/lambda-http/src/ext.rs にexampleがあったのでそれを参考にいじってみた結果、とりあえず動いた。

``` xy.json
{
  "name": "bob",
  "x": 1,
  "y": 2
}
```

``` 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;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Debug,Deserialize,Default)]
struct Args {
  #[serde(default)]
  x: usize,
  #[serde(default)]
  y: usize,
  #[serde(default)]
  name: String
}

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

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
  let args: Args = serde_json::from_slice(e.body().as_ref()).unwrap();
  info!("{:#?}", args);
  Ok(
   Response::new(
      format!(
       "{}: {} + {} = {}",
       args.name,
       args.x,
       args.y,
       args.x + args.y
      ).into()
    )
  )
}
```

```
$  curl -XPOST -H "Accept: application/json" -H "Content-type: application/json" "https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/hello?first_name=bob" -d @xy.json
bob: 1 + 2 = 3
```

おっけー。json読めてる。
Rustはまだわかってないので見よう見まねですが。

## Hello worldしてみる

そんなわけでgoogle homeに適当なサンプル作ってjson投げてみる事にします。
このへん書いてると大変なのでサックリとでお許しください。

### actions on google

- サンプルアプリ作る

![58.png](https://qiita-image-store.s3.amazonaws.com/0/308709/b01899d4-7690-e33d-0529-9a3031530a6b.png)

カテゴリは深く考えず、Games & funにしときました。
Display nameはとりあえずRust Sample。また後でちゃんと作ります。

- Custom intent

buildメニューではCustom intentを選択し、Dialog Flowを開きます。

![59.png](https://qiita-image-store.s3.amazonaws.com/0/308709/779b8e2d-1f61-52b8-1d3a-d3b49a437a51.png)

### dialog flow

- Enable Webhook

アプリのネームと同じprojectを作ったのち、おもむろにDefault Welcome Intentを開いて、**Enable webhook call for this intent**を有効にし、いきなりwebhookに投げる設定にします。
なお、defaultのレスポンスはややこしいから消しました。

![60.png](https://qiita-image-store.s3.amazonaws.com/0/308709/6041dead-f65d-0973-cda8-eac98a03350e.png)

- webhook url

fullfillmentを開いて、webhookの所にurlを入力します。

![61.png](https://qiita-image-store.s3.amazonaws.com/0/308709/5afd057e-e233-c662-3f25-1ef92fccbfdc.png)

## RequestとResponse

Dialog flowから投げられるRequestと期待されてるResponseを確認しておきます。
Welcomeの例

``` Request
{
  "responseId": "e7036551-378f-4f7c-a177-3a715936c011",
  "queryResult": {
    "queryText": "こんにちは",
    "action": "input.welcome",
    "parameters": {},
    "allRequiredParamsPresent": true,
    "intent": {
      "name": "projects/rust-sample-f3e72/agent/intents/xxxxxxxx-523d-4aa6-a864-49a6ce3236c3",
      "displayName": "Default Welcome Intent"
    },
    "intentDetectionConfidence": 1,
    "languageCode": "ja"
  },
  "originalDetectIntentRequest": {
    "payload": {}
  },
  "session": "projects/rust-sample-f3e72/agent/sessions/xxxxxxxx-14f5-22d0-e2c2-0831282d5ce2"
}
```

``` Response
{
  "fulfillmentText": "Welcome to my agent!",
  "outputContexts": []
}
```

ざっくりいうと、queryResult.queryTextを受け取ってfulfillmentTextを返せればおっけー。
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;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

#[derive(Debug,Deserialize,Default)]
struct QueryResult {
  #[serde(default)]
  queryText: String
}
#[derive(Debug,Deserialize,Default)]
struct Req {
  #[serde(default)]
  queryResult: QueryResult
}

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

    Ok(())
}

fn my_handler(e: Request, c: Context) -> Result<Response<Body>, HandlerError> {
  let query: Req = serde_json::from_slice(e.body().as_ref()).unwrap();
  info!("{:#?}", query);
  Ok(
   Response::new(
      format!(
       r#"{{"fulfillmentText": "you said {}"}}"#,
       query.queryResult.queryText
      ).into()
    )
  )
}
```

![62.png](https://qiita-image-store.s3.amazonaws.com/0/308709/ad82600d-dc5a-de40-46bd-f618191d576c.png)

でもアプリ側で日本語が文字化けしてる。。

こういう感じの問題でしょうか。
https://qiita.com/aoriso/items/a16a981d00d041c8f6a2
> Lambdaで設定するレスポンスのヘッダに"charset=UTF8"を追加したら解決した

とありますので、

```
  Ok(
   Response::builder()
     .status(200)
     .header("Content-Type", "application/json; charset=UTF-8")
     .body(
       format!(
         r#"{{"fulfillmentText": "you said {}"}}"#,
         query.queryResult.queryText
         ).into()
      )
     .expect("none")
  )
```

Response返すとこでContent-Type指定してやったら直りました。

![63.png](https://qiita-image-store.s3.amazonaws.com/0/308709/6befd9cd-cbdc-c491-93cb-7cf37684b325.png)

ちなみにCargo.tomlはこんな感じになってます。

```
[package]
name = "hello"
version = "0.1.0"
edition = "2018"

[dependencies]
lambda_runtime = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }
lambda_http = { git = "https://github.com/awslabs/aws-lambda-rust-runtime.git" }
log = "^0.4"
simple_logger = "^1"
serde = "^1.0"
serde_derive = "^1.0"
serde_json = "^1.0"
```

そんなわけで中編はここまで。
もう中編だけど何作るかは決まっておりません。

しかし、C言語、C++に代わる言語って話だったけど、触ってみた感じはnodeとかの方が似てるかなー。と感じる。
でもってRustはまだexampleから見よう見まねしてるだけの段階なので何もわかっていません。作りながら調べつつ覚えていく感じなので生暖かく見守っていてくださいませ。