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を含む型も拾いたいため
/\A#<.*:0x\h+/- VISITING_KEY =
組み立て中のオブジェクトを記録するキー
MEMO: Thread.currentはスレッドではなくFiber単位で値を持つ。組み立ての 途中でFiberをまたぐことはないため、これで取り違えは起きない
:decentworks_hexdigest_support_visiting- VERSION =
"0.1.0"
Class Method Summary collapse
-
.configuration ⇒ ::Decentworks::HexdigestSupport::Configuration
設定.
-
.configure {|configuration| ... } ⇒ void
設定の変更.
-
.detect_circular_reference(object) ⇒ Object
組み立て中のオブジェクトを記録しながらブロックを実行する.
-
.format_rational(rational) ⇒ Object
有理数を一意な文字列へ整形する.
-
.quote(value) ⇒ ::String
値を引用・エスケープする.
-
.reset_configuration! ⇒ nil
設定のリセット(主にテスト用).
-
.salt ⇒ ::String
ハッシュ値化の入力に前置するソルト.
-
.validate_source!(source, object) ⇒ void
値が決定的かを検査する.
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: ダイジェストの値はソルトに依存するため、永続化済みの値がある状態で ソルトを変更すると過去の値と一致しなくなる点に注意
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" が衝突する)
38 |
# File 'lib/decentworks/hexdigest_support/input.rb', line 38 def quote(value) = %("#{value.to_s.gsub(/[\\"]/) { |char| "\\#{char}" }}") |
.reset_configuration! ⇒ nil
設定のリセット(主にテスト用)
32 |
# File 'lib/decentworks/hexdigest_support/configuration.rb', line 32 def reset_configuration! = @configuration = nil |
.salt ⇒ ::String
ハッシュ値化の入力に前置するソルト
MEMO: 未設定(nil)はソルトなし(空文字)として扱う
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 |