Module: Decentworks::HexdigestSupport

Defined in:
lib/decentworks/hexdigest_support/input.rb,
lib/decentworks/hexdigest_support/version.rb,
lib/decentworks/hexdigest_support/time_like.rb,
lib/decentworks/hexdigest_support/numeric_like.rb,
lib/decentworks/hexdigest_support/configuration.rb,
lib/generators/decentworks/hexdigest_support/install/install_generator.rb,
sig/decentworks/hexdigest_support/input.rbs,
sig/decentworks/hexdigest_support/version.rbs,
sig/decentworks/hexdigest_support/time_like.rbs,
sig/decentworks/hexdigest_support/configuration.rbs

Defined Under Namespace

Modules: Generators, NumericLike, TimeLike Classes: CircularReferenceError, Configuration, NonDeterministicSourceError

Constant Summary collapse

DEFAULT_TO_S_PATTERN =

既定の#to_sが返す文字列(例: "#Object:0x00007f9e0c0d1234")

MEMO: メソッドの定義元(#to_sのowner)ではなく結果の文字列で判定する。 Proc#to_sや無名クラスのModule#to_sのように、独自の#to_sを持ちながら オブジェクトIDを含む型も拾いたいため

Returns:

  • (::Regexp)
/\A#<.*:0x\h+/
VISITING_KEY =

組み立て中のオブジェクトを記録するキー

MEMO: Thread.currentはスレッドではなくFiber単位で値を持つ。組み立ての 途中でFiberをまたぐことはないため、これで取り違えは起きない

:decentworks_hexdigest_support_visiting
VERSION =

Returns:

  • (String)
"0.1.0"

Class Method Summary collapse

Class Method Details

.configuration::Decentworks::HexdigestSupport::Configuration

設定



23
# File 'lib/decentworks/hexdigest_support/configuration.rb', line 23

def configuration = @configuration ||= ::Decentworks::HexdigestSupport::Configuration.new

.configure {|configuration| ... } ⇒ void

This method returns an undefined value.

設定の変更

MEMO: ダイジェストの値はソルトに依存するため、永続化済みの値がある状態で ソルトを変更すると過去の値と一致しなくなる点に注意

Yields:

Yield Parameters:

Yield Returns:

  • (void)


29
# File 'lib/decentworks/hexdigest_support/configuration.rb', line 29

def configure = yield(configuration)

.detect_circular_reference(object) ⇒ Object

組み立て中のオブジェクトを記録しながらブロックを実行する

MEMO: 自身を含む値をそのまま辿ると再帰が終わらず、StandardErrorを継承しない SystemStackErrorになる。呼び出し側のrescueをすり抜けてプロセスを 落としてしまうため、自前で検出して例外にする

MEMO: 記録するのは現在辿っている経路だけで、組み立てが終わった時点で取り除く。 同じオブジェクトが兄弟として複数回現れるのは循環ではないため、経路に 残っている場合だけを循環とみなす

MEMO: 自身を含まない深いネスト(1万段など)はSystemStackErrorのまま。循環と 違って有限であり、深さの上限を決め打ちすると正当な構造まで弾いてしまう



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/decentworks/hexdigest_support/input.rb', line 75

def detect_circular_reference(object)
  visiting = (::Thread.current[VISITING_KEY] ||= [])
  raise_circular_reference(object) if visiting.include?(object.object_id)

  visiting.push(object.object_id)

  begin
    yield
  ensure
    visiting.pop
  end
end

.format_rational(rational) ⇒ Object

有理数を一意な文字列へ整形する

MEMO: 有限小数で表せる場合は十進表記、表せない場合は分数表記にする。Rationalは 既約かつ分母が正へ正規化済みのため、どちらの表記も数に対して一意になる

MEMO: 分数表記が十進表記と衝突することはない。十進表記に区切り文字(/)は 現れないため



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/decentworks/hexdigest_support/numeric_like.rb', line 53

def format_rational(rational)
  return rational.numerator.to_s if rational.denominator == 1

  scale = decimal_scale(rational.denominator)
  return "#{rational.numerator}/#{rational.denominator}" unless scale

  sign   = rational.negative? ? "-" : ""
  digits = (rational.numerator.abs * (10**scale / rational.denominator)).to_s.rjust(scale + 1, "0")

  "#{sign}#{digits[0...-scale]}.#{digits[-scale..]}"
end

.quote(value) ⇒ ::String

値を引用・エスケープする

MEMO: #inspectを使わない。#inspectは非ASCII文字をEncoding.default_externalが 印字可能かどうかでエスケープするか決めるため、同じ値でも実行環境の ロケール次第で異なる入力になってしまう (UTF-8環境では"あ"、US-ASCII環境では"あ")

MEMO: エスケープ対象は引用符とバックスラッシュのみ。UTF-8環境の#inspectと 同じ出力になるため、制御文字を含まない値のダイジェストは変わらない

MEMO: 引用しないと、値に区切り文字(:)が含まれる場合に型名との境界が 曖昧になる(例: Foo::Barの"x" と Fooの":Bar:x" が衝突する)

Parameters:

Returns:

  • (::String)


38
# File 'lib/decentworks/hexdigest_support/input.rb', line 38

def quote(value) = %("#{value.to_s.gsub(/[\\"]/) { |char| "\\#{char}" }}")

.reset_configuration!nil

設定のリセット(主にテスト用)

Returns:

  • (nil)


32
# File 'lib/decentworks/hexdigest_support/configuration.rb', line 32

def reset_configuration! = @configuration = nil

.salt::String

ハッシュ値化の入力に前置するソルト

MEMO: 未設定(nil)はソルトなし(空文字)として扱う

Returns:

  • (::String)


37
# File 'lib/decentworks/hexdigest_support/configuration.rb', line 37

def salt = configuration.salt.to_s

.validate_source!(source, object) ⇒ void

This method returns an undefined value.

値が決定的かを検査する

MEMO: 既定のObject#to_sはオブジェクトIDを含むため、#to_sも #to_hexdigest_sourceも実装していないオブジェクトのダイジェストは プロセスごとに変わる。永続化した後で気付くと復旧できないため、 黙って通さず例外にする

MEMO: StringとSymbolは検査しない。検査は「既定のObject#to_sへ落ちていないか」を 見るためのものだが、この2つは値そのものが文字列であり、オブジェクトIDが 混入する経路がない。除外しないと "#User:0x00007f9e0c0d1234" のような 正当な文字列(ログの1行や#inspectの結果を保持した値)が例外になってしまう

MEMO: 裏を返すと、利用側が自分でオブジェクトを文字列化して渡した場合は検出 できない。gemから見ればただの文字列であり、他の文字列と区別できないため



54
55
56
57
58
59
60
61
# File 'lib/decentworks/hexdigest_support/input.rb', line 54

def validate_source!(source, object)
  return if object.is_a?(::String) || object.is_a?(::Symbol)
  return unless DEFAULT_TO_S_PATTERN.match?(source.to_s)

  raise ::Decentworks::HexdigestSupport::NonDeterministicSourceError,
        "#{object.class}の値がオブジェクトIDを含むため、ダイジェストが決定的になりません" \
        "#{source})。#to_hexdigest_sourceか#to_sを実装してください"
end