Shogo's Blog

Aug 13, 2024 - 2 minute read - go golang typescript javascript

Structured Field Values のパーサーを書いた

GoとTypeScriptで Structured Field Values のパーサーを書きました。

背景

そもそも Structured Field Values (SFV) とはなにか、なぜ登場したのか、という背景はこちらの記事をどうぞ。

HTTP APIを開発していると、アプリケーション独自のHTTPフィールドを定義することがあります。 そういうときに、標準にしたがっておいたほうが何かと楽だろう、ということでSFVを採用しました。

しかしいい感じのSFVのパーサーがなかなか見つからなかったので、自作することにした、というわけです。

Go実装

Go のモジュールとして公開されているので、いつものように go get してきましょう。

go get github.com/shogo82148/go-sfv

GoでSFVをパースする

DecodeItemDecodeListDecodeDictionary を使います。

net/http.Header.Valuesの戻り値を直接受け取れるよう、 各関数は []string を受け取るようにしました。

package main

import (
	"fmt"
	"net/http"

	"github.com/shogo82148/go-sfv"
)

func main() {
	h := make(http.Header)
	h.Add("Example", `2; foourl="https://foo.example.com/"`)

	item, err := sfv.DecodeItem(h.Values("Example"))
	if err != nil {
		panic(err)
	}
	fmt.Println(item.Value)
	fmt.Println(item.Parameters.Get("foourl"))
	// Output:
	// 2
	// https://foo.example.com/
}

item.Valueinterface{} 型で返ってくるので、必要に応じて型アサーションを行います。

switch val := item.Value.(type) {
case int64:             // Integers
case float64:           // Decimals
case string:            // Strings
case sfv.Token:         // Tokens
case bool:              // Booleans
case time.Time:         // Dates
case sfv.DisplayString: // Display Strings
}

GoでSFVをエンコードする

EncodeItemEncodeListEncodeDictionaryを使用します。

package main

import (
	"fmt"

	"github.com/shogo82148/go-sfv"
)

func main() {
	item := sfv.Item{
		Value: 2,
		Parameters: sfv.Parameters{
			{
				Key:   "foourl",
				Value: "https://foo.example.com/",
			},
		},
	}

	fmt.Println(sfv.EncodeItem(item))
	// Output:
	// 2;foourl="https://foo.example.com/" <nil>
}

TypeScript実装

NodeとDenoに対応しています。

npmからインストールする場合は、

npm install --save @shogo82148/sfv

jsrからインストールする場合は、

deno add @shogo82148/sfv

です。

TypeScriptでSFVをパースする

decodeItemdecodeListdecodeDictionary を使います。

import { decodeItem } from "@shogo82148/sfv";

const item = decodeItem('2; foourl="https://foo.example.com/"')
console.log(item.value); // Integer { value: 2 }
console.log(item.parameters.get("foourl")); // https://foo.example.com/

instanceoftypeof を使って型を判断します。

const value = item.value;
if (value instanceof Integer) {
  // Integers
}
if (value instanceof Decimal) {
  // Decimals
}
if (typeof value === "string") {
  // Strings
}
if (value instanceof Token) {
  // Tokens
}
if (value instanceof Uint8Array) {
  // Binary Sequences
}
if (typeof value === "boolean") {
  // Booleans
}
if (value instanceof Date) {
  // Dates
}
if (value instanceof DisplayString) {
  // Display Strings
}

TypeScriptでSFVをエンコードする

encodeItemencodeListencodeDictionary を使用します。

import { encodeItem, Integer, Item, Parameters } from "@shogo82148/sfv";

const item = new Item(
  new Integer(2),
  new Parameters([["foourl", "https://foo.example.com/"]])
);

console.log(encodeItem(item)); // 2;foourl="https://foo.example.com/"

Integer と Decimal について

TypeScript版で特徴的なのは Integer 型と Decimal 型の存在です。 TypeScript自体には「整数」を表す型はなく、すべて number 型で表されます。 でもそこはしっかり区別して欲しい、ということで Integer 型と Decimal 型を独自に定義しました。

Integer 型と Decimal 型は valueOf メソッドで元の値を取り出せます。 Decimal 型は引数にたとえ整数が渡されたとしても、小数としてシリアライズします。

const i = new Integer(42);
console.log(i.valueOf()); // 42
console.log(encodeItem(new Item(i))); // 42

const d = new Decimal(42);
console.log(d.valueOf()); // 42
console.log(encodeItem(new Item(d))); // 42.0

まとめ

GoとTypeScriptで Structured Field Values のパーサーを書きました。

ぜひ利用してみてください。フィードバックをお待ちしてます。

うさぎの耳はぴょんと立ち、
新しいSFVに心は躍る。
コードの中で踊るデータ、
標準化の夢、実現への道。
みんなで集まり、喜びを分かち合う、
うさぎたちの楽しい冒険が始まる! 🐰✨

by CodeRabbit

参考