dasel, air, axum を試してみた。
dasel
jq, yq などの データパースを 拡張して、toml, xml, csv などの複数のデータ型に対応させたツール。all in one なところが強み。
https://github.com/TomWright/dasel
サブコマンドの select は jq などと同様にデータを取得する。軽く試してみたけれども、jq とは文法が少し違いそう。
$ aws ec2 describe-instances > /tmp/ec2.json $ dasel select -f /tmp/ec2.json 'Reservations[]'
この あたりを見ながら試しているけれども、どうも微妙そう。 jq の文法を引き継いでほしかった。
$ aws ec2 describe-instances | dasel -p json -m '.Reservations.-' "0" "1" "2"
そうはならんやろ...? 他に put という書き込みをすることも可能になる。これで yaml を更新すれば kubernetes などの設定ファイル管理も少しは楽になりそうな。
こっちはある程度は正常に動作していそう。
$ aws ec2 describe-instances | dasel put string -p json '.email' 'contact@tomwright.me' | tail "VirtualizationType": "hvm", "VpcId": "vpc-XXX" } ], "OwnerId": "XXX", "ReservationId": "r-XXX" } ], "email": "contact@tomwright.me" }
put だけなら使って見ても良いかもしれない。
air
golang の アプリケーションサーバー開発にて、オートリロードを提供するライブラリ。 fresh という同系統のものが存在するが、作成者はどうやらお気に召さなかった様子。
https://daseldocs.tomwright.me/selectors/keys-and-indexes
ひとまず gin の小さなアプリケーションを作る。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") }
air の設定ファイルを生成。せっかくなので dasel で見てみるw。
$ air init __ _ ___ / /\ | | | |_) /_/--\ |_| |_| \_ 1.27.3, built with Go 1.16.3 .air.toml file created to the current directory with the default settings $ cat .air.toml | dasel -p toml root = "." tmp_dir = "tmp" [build] bin = "./tmp/main" cmd = "go build -o ./tmp/main ." delay = 1000 exclude_dir = ["assets", "tmp", "vendor"] exclude_file = [] exclude_regex = [] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] include_ext = ["go", "tpl", "tmpl", "html"] kill_delay = "0s" log = "build-errors.log" send_interrupt = false stop_on_error = true [color] app = "" build = "yellow" main = "magenta" runner = "green" watcher = "cyan" [log] time = false [misc] clean_on_exit = false
特に何も変更せずに使用してみたところいけてそう。使用感は良さそう。main.go を変更すると自動でサーバーが再起動された。 別に gin じゃなくても動きそう。
$ air -c .air.toml ... [GIN] 2021/08/07 - 19:48:31 | 200 | 34.703µs | ::1 | GET "/ping" main.go has changed building... running... [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /ping --> main.main.func1 (3 handlers) [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default [GIN-debug] Listening and serving HTTP on :8080 [GIN] 2021/08/07 - 19:48:33 | 200 | 56.977µs | ::1 | GET "/ping"
golang のアプリケーションサーバーを開発する際には便利そう。
axum
hyper に薄い wrapper をかぶせて使いやすい形に変更した web-framework のようです。いうだけあって、サンプルを見る限りではかなりいい感じです。 癖もなく、使いやすそう。
https://github.com/tokio-rs/axum
適当に todo アプリを実装してみます。
use axum::{ extract::Extension, http::StatusCode, prelude::*, response::IntoResponse, AddExtensionLayer, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::Mutex; use uuid::Uuid; type TodoRepository = HashMap<String, Todo>; type SheredState = Arc<Mutex<TodoRepository>>; #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let todos: TodoRepository = HashMap::new(); let state = Arc::new(Mutex::new(todos)); let app = route("/", get(root)) .route("/todos", get(list_todos)) .route("/todos/post", post(create_todo)) .route("/todos/delete", delete(delete_todo)) .layer(AddExtensionLayer::new(state)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) .await .unwrap(); } async fn root() -> &'static str { "Hello, World!" } async fn list_todos(Extension(state): Extension<SheredState>) -> impl IntoResponse { let mut users = vec![]; let todos = state.lock().await; for (k, v) in todos.iter() { users.push(v.clone()) } (StatusCode::OK, response::Json(users.clone())) } async fn create_todo( Extension(state): Extension<SheredState>, extract::Json(payload): extract::Json<Todo>, ) -> impl IntoResponse { let uuid = Uuid::new_v4().to_string(); let mut todos = state.lock().await; todos.insert( uuid.clone(), Todo { id: Some(uuid.clone()), title: payload.title, body: payload.body, }, ); let todo = todos.get(&uuid).unwrap(); (StatusCode::OK, response::Json(todo.clone())) } async fn delete_todo( Extension(state): Extension<SheredState>, extract::Json(payload): extract::Json<Todo>, ) -> impl IntoResponse { let mut todos = state.lock().await; let id = payload.id.clone().unwrap(); todos.remove(&id).unwrap(); (StatusCode::OK, response::Json(payload)) } #[derive(Deserialize, Serialize, Clone)] struct Todo { id: Option<String>, title: Option<String>, body: Option<String>, }
なかなか良さそうな感じではあります。唯一 router だけはどうもうまく動作しませんでした。以下のように記述した後に アクセスしてみます。
let app = route("/", get(root)) .route("/todos", get(root)) .route("/todos", post(root)) .route("/todos", delete(root));
どうも path が同じだと methods の設定を食い合う...。
$ for i in GET POST DELETE; do curl -i -X $i localhost:3000/todos; done HTTP/1.1 405 Method Not Allowed content-length: 0 date: Sat, 07 Aug 2021 15:14:12 GMT HTTP/1.1 405 Method Not Allowed content-length: 0 date: Sat, 07 Aug 2021 15:14:12 GMT HTTP/1.1 200 OK content-type: text/plain content-length: 13 date: Sat, 07 Aug 2021 15:14:12 GMT Hello, World!
コードを見るとコメントの箇所に正しい設定が書いてありました。僕が悪いだけでした 笑。