日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

SwiftでJSON文字列をStructのEnumやDate型にbindする

こんにちは、最近Swiftをやっていて、Scalaのコレクション操作が欲しくなってよくないなってなっています。
API等でJSON文字列を受け取って特定のオブジェクトなどにデータを入れたいみたいなのはよくあることだと思うのですが、
Swiftでやったのは初だったのでそのあたりのメモがてらの備忘録です。

SwiftのVersion

$swift --version
Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53)
Target: x86_64-apple-darwin19.5.0

StringやInt、Arrayのbind

文字列、数字や配列など、jsonにもある型は特段なにもしなくてもbindしてくれますね。
JSONDecoderになにも設定せずDecodableを実装したstructの変数名とkey名を合わせると勝手にbindしてくれました。

import Foundation

let data = """
{
    "a": {
        "b": "xxx",
        "b2": ["xxx", "yyy"]
    }
}
""".data(using: .utf8)!

struct A: Decodable { 
    let a: B
}

struct B: Decodable {
    let b: String
    let b2: [String]
}

let decoder = JSONDecoder()
let t1  = try decoder.decode(A.self, from: data)
print(t1)

実行

$swift main1.swift
A(a: main1.B(b: "xxx", b2: ["xxx", "yyy"]))

Date型のbind

やはりあるあるなのか、JSONDecoderにdateDecodingStrategyというものが用意されていました。
.iso8601フォーマットは既に用意されていて、そのフォーマットならそれを設定しておけば使えるみたいですね。
独自フォーマットにする場合はformattedメソッドにDateFormatterを渡してあげると良いみたいでした。

import Foundation

let data = """
{
    "a": {
        "b": "xxx",
        "c": "2020-07-08T15:00:00+09:00",
    }
}
""".data(using: .utf8)!

struct A: Decodable { 
    let a: B
}

struct B: Decodable {
    let b: String
    let c: Date
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let t1  = try decoder.decode(A.self, from: data)
print(t1)

decoder.dateDecodingStrategy = .formatted({
        let f = DateFormatter()
        f.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        return f
        }())
let t2  = try decoder.decode(A.self, from: data)
print(t2)

実行

$swift main2.swift
A(a: main2.B(b: "xxx", c: 2020-07-08 06:00:00 +0000))
A(a: main2.B(b: "xxx", c: 2020-07-08 06:00:00 +0000))

Enum型のbind

Enum型はEnum型にDecodableを実装して、文字列を元にしたいのであればStringを継承しておけば良い感じでした。

import Foundation

let data = """
{
    "a": {
        "d": [
            "hoge",
            "fuga",
            "piyo"
        ]
    }
}
""".data(using: .utf8)!

struct A: Decodable { 
    let a: B
}

struct B: Decodable {
    let d: Array<D>

    enum D: String, Decodable {
        case HOGE = "hoge"
        case FUGA = "fuga"
        case PIYO = "piyo"
    }
}

let decoder = JSONDecoder()
let t1  = try decoder.decode(A.self, from: data)
print(t1)

実行

$swift main3.swift
A(a: main3.B(d: [main3.B.D.HOGE, main3.B.D.FUGA, main3.B.D.PIYO]))

かなり簡単でしたね。
これで大体のものはAPIから内部のデータに変換できそうです。
めでたしめでたし。

AstroゲーミングヘッドセットA50の電源が不具合でつかなくて困った話

無線のゲーミングヘッドセットのAstro A50を買って使っているんですが、それが電源がつかなくなり困りました。
ぐぐったら初期化っぽい方法を書いてくれている人がいて、すごく助かったのでそのメモです。

Astro A50はこれ

引用元とリセットについて

助かった記事がこちら
http://nepr.info/astro-gaming-astro-astro-sans-fil-a50-gen-3-ps4-pc-%E3%83%8F%E3%82%BA%E3%83%AC%E5%BC%95%E3%81%84%E3%81%9F%E3%80%82/

引用すると、

ネットで検索すると結構海外の方で同じ症状に陥っていたが復旧出来たという書き込みがあり自分も実践してみると何事もなく復旧出来てしまったのでその方法を紹介します。
①電源スイッチをon位置にする(ledランプが点灯しなくてもok)
②ヘッドセッド右側にあるgameボタンとdolbyボタンを同時押しで30秒間押したままにする
この方法(リセット?)で復旧することが出来ました

リセットボタンの位置

ゲームボタンがここで

Gameボタン

Dolbyボタンがここですね

Dolbyボタン

自分のヘッドホンは電源のLEDが一切つかなかったんですが、ここを30秒押したらつくようになりました。
その他なんか充電しても充電が出来ないとかそういうときにここのリセットすると直ったりしたのでよかったです。

その後更にファームウェア更新の失敗に困ったときの対応

こちらもオススメ

arata.hatenadiary.com arata.hatenadiary.com arata.hatenadiary.com

JupyterLab上でjupyter-lspを使って補完してもらう

jupyter全然触ってなかったらjupyter labってのが出ててへーってなったりしてました。
Tabとか押したら補完してくれるんですが、もっとリッチにできないかなーと思ってらLanguageServerのextensionがあったので、今回はそれを試してみました。

動いたレポジトリはここ

GitHub - ara-ta3/jupyterlab-getting-started

$make install install/extension run

# pythonのパスがpython3じゃない場合は
$make install install/extension run PYTHON=python

で動くはずです

インストール

Installは本家を参考にやりました。
唯一困ったのは、そのままやってもpython用のLanguageServerが起動しなくて jupyter_notebook_config.json に追加したところでした。

https://github.com/krassowski/jupyterlab-lsp#installation

0. venvの設定とnode.jsの追加

デフォで入ってたpython3とnodebrewで入れたnodeを使いました。
nodeはjupyter-lspで使うらしいです。

$python3 -V
Python 3.7.6

$python3 -m venv venv

$source venv/bin/activate

$node -v
v12.15.0

1. pipでjupyterやらlspやらをインストール

$cat requirements.txt
jupyterlab
jupyter-lsp
python-language-server[all]

$ pip install -r requirements.txt
# ... 

2. jupyter extensionをインストール

$jupyter labextension install @krassowski/jupyterlab-lsp
# ...

# installされたversion
$jupyter --version
jupyter core     : 4.6.3
jupyter-notebook : 6.0.3
qtconsole        : not installed
ipython          : 7.12.0
ipykernel        : 5.1.4
jupyter client   : 5.3.4
jupyter lab      : 1.2.6
nbconvert        : 5.6.1
ipywidgets       : not installed
nbformat         : 5.0.4
traitlets        : 4.3.3

3. jupyter_notebook_config.jsonに設定追加

{
  "LanguageServerManager": {
    "language_servers": {
      "python": {
        "version": 1,
        "argv": ["python", "-m", "pyls"],
        "languages": ["python"]
      }
    }
  }
}

起動

$jupyter lab
# ...

ちゃんと出ました。便利。

Python3の補完

参考

qiita.com

localstack上でSQS->Lambda(Scala製)の連携を動かしてみる

開発時にlocalstackをAmazon Resourceのモックとして使っていて、
前試してなんか動かなかったんだけど、ちゃんとやったら動いたのでそのメモです。

github.com

localstack上のSQSにメッセージを送ったら、localstack上でmappingされたScala製のLambdaがキックされる様子を観測するのがゴールです。
検証用に使ったコードはこちらです。

github.com

登場するコマンドたちのversion

$aws --version
aws-cli/1.16.260 Python/3.7.4 Darwin/18.7.0 botocore/1.12.250

$docker-compose --version
docker-compose version 1.24.1, build 4667896b

1. localstackの起動

docker-composeを利用しつつlocalstackのSQSとLambdaを起動します。
利用したlocalstackのversionは 0.10.6 です。
networksは内部で別のコンテナと通信したいときに指定するために使います。
(実は今回は使ってないですごめんね)

docker-compose.yml

version: "2"

services:
    localstack:
        image: localstack/localstack:0.10.6
        ports:
          - "4567-4597:4567-4597"
        environment:
            LAMBDA_EXECUTOR: docker
            DOCKER_HOST: unix:///var/run/docker.sock
            DATA_DIR: /tmp/localstack/data
            SERVICES: sqs,lambda
            DEBUG: 1
            # 内部APIを叩きたいときなどに使う
            LAMBDA_DOCKER_NETWORK: localstack-sqs-lambda_foo_network
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock"
        networks:
            foo_network:

networks:
    foo_network:

2. Lambda Handlerの作成

特に強い意図はないのですが仕事で触れていたのがScalaだったのでScalaで書いています。
HelloWorldとメッセージに含まれるBodyを表示する感じです。

scala/src/main/scala/com/ru/waka/FooHandler.scala

package com.ru.waka

import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
import com.amazonaws.services.lambda.runtime.events.SQSEvent
import scala.collection.JavaConverters._

class FooHandler extends RequestHandler[SQSEvent, Unit] {
  override def handleRequest(input: SQSEvent, context: Context): Unit = {
    context.getLogger.log("Hello World from Scala Code\n")
    context.getLogger.log("Message's body is...\n")
    input.getRecords.asScala.foreach(r => {
      context.getLogger.log(s"${r.getBody}")
    })
  }
}

Lambdaにわたす際のビルドにはsbt-assemblyを利用しています。

github.com

ビルドの様子

$make -C scala build
# Lambdaのコードビルド
make[1]: Entering directory '/Users/arata/.ghq/github.com/ara-ta3/localstack-sqs-lambda/scala'
./tools/sbt/bin/sbt assembly
[info] Loading settings for project scala-build from build.sbt ...
[info] Loading project definition from /Users/arata/.ghq/github.com/ara-ta3/localstack-sqs-lambda/scala/project
[info] Loading settings for project scala from build.sbt ...
[info] Set current project to scala (in build file:/Users/arata/.ghq/github.com/ara-ta3/localstack-sqs-lambda/scala/)
[info] Strategy 'discard' was applied to 32 files (Run the task at debug level to see details)
[info] Strategy 'filterDistinctLines' was applied to a file (Run the task at debug level to see details)
[info] Assembly up to date: /Users/arata/.ghq/github.com/ara-ta3/localstack-sqs-lambda/scala/target/scala-2.12/scala-assembly-0.1.0-SNAPSHOT.jar
[success] Total time: 1 s, completed 2019/12/20 22:50:41
make[1]: Leaving directory '/Users/arata/.ghq/github.com/ara-ta3/localstack-sqs-lambda/scala'

3. SQSとLambdaの作成

次にlocalstack上にSQSとLambda、そしてそのマッピングを作成します。
Lambda作成時には 2. でビルドした成果物のjarファイルを上げています。

# SQSの作成
$aws --endpoint-url http://localhost:4576 \
    sqs create-queue --queue-name 'foo-queue'
{
    "QueueUrl": "http://localhost:4576/queue/foo-queue"
}

# Lambdaの作成
$aws --endpoint-url http://localhost:4574 \
    lambda create-function \
    --function-name 'foo-function' \
    --runtime=java8 \
    --role=dummyrole \
    --handler=com.ru.waka.FooHandler \
    --zip-file=fileb://./scala/target/scala-2.12/scala-assembly-0.1.0-SNAPSHOT.jar
{
    "FunctionName": "foo-function",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:foo-function",
    "Runtime": "java8",
    "Role": "dummyrole",
    "Handler": "com.ru.waka.FooHandler",
    "CodeSize": 10857096,
    "Description": "",
    "Timeout": 3,
    "LastModified": "2019-12-20T13:50:43.397+0000",
    "CodeSha256": "VJ4FGoNtdijTBO5acf/pu5WW/K/FQFytSH+x9WprcQc=",
    "Version": "$LATEST",
    "TracingConfig": {
        "Mode": "PassThrough"
    },
    "RevisionId": "fbaff8d2-6ac0-4498-879a-57b87cdd1466"
}

# SQSとLambdaのマッピング
$aws --endpoint-url http://localhost:4574 \
    lambda create-event-source-mapping \
    --event-source-arn arn:aws:sqs:us-east-1:000000000000:foo-queue  \
    --function-name "foo-function"
{
    "UUID": "b07dc12c-5a74-490b-82b4-1e43abdb6761",
    "BatchSize": 100,
    "EventSourceArn": "arn:aws:sqs:us-east-1:000000000000:foo-queue",
    "FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:foo-function",
    "LastModified": 1576849845.0,
    "LastProcessingResult": "OK",
    "State": "Enabled",
    "StateTransitionReason": "User action"
}

これでSQSにメッセージを送ればLambdaがキックされてHello Worldが表示されるはずです。

4. メッセージを送る

aws cliからメッセージを送ってみました。
そうするとdocker-composeのログにINFOログが流れてきたのがわかります。

$aws --region us-east-1 --endpoint-url http://localhost:4576 \
    sqs send-message \
    --queue-url 'http://localstack:4576/queue/foo-queue' \
    --message-body 'hogehoge'
{
    "MD5OfMessageBody": "329435e5e66be809a656af105f42401e",
    "MD5OfMessageAttributes": "d41d8cd98f00b204e9800998ecf8427e",
    "MessageId": "c3aa9a0c-9c3d-4b61-a426-9d45a2b2595f"
}

# docker-composeのログ
localstack_1  | 13:50:42.627 [elasticmq-akka.actor.default-dispatcher-4] INFO  o.elasticmq.actor.QueueManagerActor - Creating queue QueueData(foo-queue,MillisVisibilityTimeout(30000),PT0S,PT0S,2019-12-20T13:50:42.597Z,2019-12-20T13:50:42.597Z,None,false,false,None,None,Map())
localstack_1  | 2019-12-20T13:53:26:DEBUG:localstack.services.awslambda.lambda_api: Found 1 source mappings for event from SQS queue arn:aws:sqs:us-east-1:000000000000:foo-queue: ['arn:aws:lambda:us-east-1:000000000000:function:foo-function']
localstack_1  | 2019-12-20T13:53:26:DEBUG:localstack.services.awslambda.lambda_executors: Running lambda cmd: CONTAINER_ID="$(docker create -i  --entrypoint ""  -e DOCKER_LAMBDA_USE_STDIN="$DOCKER_LAMBDA_USE_STDIN" -e HOSTNAME="$HOSTNAME" -e LOCALSTACK_HOSTNAME="$LOCALSTACK_HOSTNAME" -e AWS_LAMBDA_FUNCTION_NAME="$AWS_LAMBDA_FUNCTION_NAME" -e AWS_LAMBDA_FUNCTION_VERSION="$AWS_LAMBDA_FUNCTION_VERSION" -e AWS_LAMBDA_FUNCTION_INVOKED_ARN="$AWS_LAMBDA_FUNCTION_INVOKED_ARN" --network="localstack-sqs-lambda_foo_network" --rm "lambci/lambda:java8" bash -c 'cd /var/task; java  -cp ".:localstack-utils-fat.jar" "cloud.localstack.LambdaExecutor" "com.ru.waka.FooHandler" "event_file.json"')";docker cp "/tmp/localstack/zipfile.2c9c80b9/." "$CONTAINER_ID:/var/task"; docker start -ai "$CONTAINER_ID";
localstack_1  | 2019-12-20T13:53:29:DEBUG:localstack.services.awslambda.lambda_executors: Lambda arn:aws:lambda:us-east-1:000000000000:function:foo-function result / log output:
localstack_1  | ()
localstack_1  | >Dec 20, 2019 1:53:28 PM cloud.localstack.LambdaContext$1 log
localstack_1  | > INFO: Hello World from Scala Code
localstack_1  | >
localstack_1  | > Dec 20, 2019 1:53:28 PM cloud.localstack.LambdaContext$1 log
localstack_1  | > INFO: Message's body is...
localstack_1  | >
localstack_1  | > Dec 20, 2019 1:53:28 PM cloud.localstack.LambdaContext$1 log
localstack_1  | > INFO: hogehoge

ハマったところ

aws cliからメッセージを送った際、Regionが $HOME/.aws/config に記述されているデフォルトのRegionを見ていて ap-northeast-1 になっていました。
しかし、アプリケーション(Java SDK)から叩いた際にRegionを ap-northeast-1 に指定しても利用されるSQSは us-east-1 になってしまいました。
そのため、上記では us-east-1 を利用するようにしています。
他の言語のSDKがどうなっているかはわからないですが、ローカルでの開発用途になると思いますし、localstackに対してaws cliで叩くときにregionのオプションをつければいいだけになるので us-east-1 でとりあえず逃げることはできそうですね。

Java SDKから叩いた際のコード

scala/src/main/scala/com/ru/waka/SQSMessageSender.scala

package com.ru.waka

import com.amazonaws.regions.Regions
import com.amazonaws.services.sqs.AmazonSQSClientBuilder
import com.amazonaws.services.sqs.model.SendMessageRequest

object SQSMessageSender {
  def main(args: Array[String]): Unit = {
    val sqs = AmazonSQSClientBuilder
      .standard()
      .withRegion(Regions.AP_NORTHEAST_1)
      .build()
    val req = new SendMessageRequest()
        .withQueueUrl("http://localhost:4576/queue/foo-queue")
        .withMessageBody("Message from Scala Code")
    sqs.sendMessage(req)
  }
}