Module: Feat::Datafile

Defined in:
lib/feat/datafile.rb

Overview

Wire-format types. JSON field names mirror @feathq/datafile-schema exactly (camelCase) — ‘from_json` keeps the hash keys, no rekeying.

Defined Under Namespace

Classes: ConditionGroupSpec, ConditionSpec, ContextKindSpec, File, FlagSpec, Rollout, RolloutVariation, RuleSpec, SegmentRuleSpec, SegmentSpec, TargetSpec, VariationSpec

Class Method Summary collapse

Class Method Details

.build_flag(d) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/feat/datafile.rb', line 82

def self.build_flag(d)
  FlagSpec.new(
    id:                             d["id"],
    key:                            d["key"],
    valueType:                      d["valueType"],
    salt:                           d["salt"],
    archived:                       d["archived"],
    isEnabled:                      d["isEnabled"],
    offVariationId:                 d["offVariationId"],
    defaultVariationId:             d["defaultVariationId"],
    defaultRollout:                 build_rollout(d["defaultRollout"]),
    defaultBucketingContextKindKey: d["defaultBucketingContextKindKey"],
    variations:                     d["variations"].map { |v| VariationSpec.new(**symbolize(v)) },
    targets:                        d["targets"].map { |t| TargetSpec.new(**symbolize(t)) },
    rules:                          d["rules"].map { |r| build_rule(r) }
  )
end

.build_rollout(d) ⇒ Object



112
113
114
115
116
117
118
119
# File 'lib/feat/datafile.rb', line 112

def self.build_rollout(d)
  return nil if d.nil?

  Rollout.new(
    bucketingContextKindKey: d["bucketingContextKindKey"],
    variations:              d["variations"].map { |v| RolloutVariation.new(**symbolize(v)) }
  )
end

.build_rule(d) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
# File 'lib/feat/datafile.rb', line 100

def self.build_rule(d)
  RuleSpec.new(
    id:                      d["id"],
    bucketingContextKindKey: d["bucketingContextKindKey"],
    variationId:             d["variationId"],
    rollout:                 build_rollout(d["rollout"]),
    groups:                  d["groups"].map { |g|
      ConditionGroupSpec.new(conditions: g["conditions"].map { |c| ConditionSpec.new(**symbolize(c)) })
    }
  )
end

.build_segment(d) ⇒ Object



121
122
123
124
125
126
127
128
# File 'lib/feat/datafile.rb', line 121

def self.build_segment(d)
  SegmentSpec.new(
    key:   d["key"],
    rules: d["rules"].map { |r|
      SegmentRuleSpec.new(conditions: r["conditions"].map { |c| ConditionSpec.new(**symbolize(c)) })
    }
  )
end

.from_json(data) ⇒ Object

Parse a wire-format hash (typically JSON.parse output) into a Datafile::File. Hash keys must already be string-keyed (default for JSON.parse).



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/feat/datafile.rb', line 37

def self.from_json(data)
  File.new(
    schemaVersion: data["schemaVersion"],
    envId:         data["envId"],
    envKey:        data["envKey"],
    projectId:     data["projectId"],
    version:       data["version"],
    etag:          data["etag"],
    generatedAt:   data["generatedAt"],
    flags:         data["flags"].each_with_object({}) { |(k, v), o| o[k] = build_flag(v) },
    segments:      (data["segments"] || {}).each_with_object({}) { |(k, v), o| o[k] = build_segment(v) },
    contextKinds:  (data["contextKinds"] || {}).each_with_object({}) { |(k, v), o| o[k] = ContextKindSpec.new(**symbolize(v)) }
  )
end

.merge_patch(current, patch) ⇒ Object

Merge a patch delta onto an existing File and return a new File. Pure: current is not mutated. Added or changed flags/segments are built from their wire objects and override by key; removed keys are dropped. version, etag, and generatedAt advance to the patch’s values (etag and generatedAt fall back to the current ones when the patch omits them). Raises if a flag or segment object is malformed; the caller treats that as a no-op and ignores the patch.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/feat/datafile.rb', line 59

def self.merge_patch(current, patch)
  flags = current.flags.dup
  (patch["flags"] || {}).each { |k, v| flags[k] = build_flag(v) }
  (patch["removedFlags"] || []).each { |k| flags.delete(k) }

  segments = current.segments.dup
  (patch["segments"] || {}).each { |k, v| segments[k] = build_segment(v) }
  (patch["removedSegments"] || []).each { |k| segments.delete(k) }

  File.new(
    schemaVersion: current.schemaVersion,
    envId:         current.envId,
    envKey:        current.envKey,
    projectId:     current.projectId,
    version:       patch["to"],
    etag:          patch["etag"] || current.etag,
    generatedAt:   patch["generatedAt"] || current.generatedAt,
    flags:         flags,
    segments:      segments,
    contextKinds:  current.contextKinds
  )
end

.symbolize(h) ⇒ Object



130
131
132
# File 'lib/feat/datafile.rb', line 130

def self.symbolize(h)
  h.each_with_object({}) { |(k, v), o| o[k.to_sym] = v }
end