Class: Jpzip::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/jpzip/client.rb

Overview

Client is the jpzip SDK entry point. Construct it once and reuse it across threads — it is safe for concurrent use.

Instance Method Summary collapse

Constructor Details

#initialize(base_url: DEFAULT_BASE_URL, cache: nil, memory_cache_size: MemoryLRU::DEFAULT_CAPACITY, on_spec_mismatch: nil, http_client: nil) ⇒ Client

Returns a new instance of Client.

Parameters:

  • base_url (String) (defaults to: DEFAULT_BASE_URL)

    override the CDN origin

  • cache (Jpzip::Cache, nil) (defaults to: nil)

    optional L2 persistent cache

  • memory_cache_size (Integer) (defaults to: MemoryLRU::DEFAULT_CAPACITY)

    L1 LRU capacity in prefix entries

  • on_spec_mismatch (Proc, nil) (defaults to: nil)

    hook invoked once when /meta.json’s spec_version differs from SPEC_VERSION

  • http_client (Proc, nil) (defaults to: nil)

    testing hook receiving a URI, returning a Net::HTTPResponse-like object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/jpzip/client.rb', line 29

def initialize(base_url: DEFAULT_BASE_URL,
               cache: nil,
               memory_cache_size: MemoryLRU::DEFAULT_CAPACITY,
               on_spec_mismatch: nil,
               http_client: nil)
  @base_url = base_url.to_s.sub(%r{/+\z}, "")
  @cache = cache
  @mem = MemoryLRU.new(memory_cache_size)
  @on_spec_mismatch = on_spec_mismatch
  @http_client = http_client
  @meta_mu = Monitor.new
  @meta_cached = nil
  @meta_resolved = false
  @known_version = nil
end

Instance Method Details

#lookup(zipcode) ⇒ Object

Lookup returns the ZipcodeEntry for zipcode or nil if not found. Malformed input returns nil without contacting the network.



47
48
49
50
51
52
53
54
# File 'lib/jpzip/client.rb', line 47

def lookup(zipcode)
  return nil unless ZIP_REGEX.match?(zipcode.to_s)

  dict = fetch_prefix_dict(zipcode[0, 3])
  return nil if dict.nil?

  dict[zipcode]
end

#lookup_allObject

LookupAll fans out across /g/0..9.json in parallel and merges. The CDN does not publish a single /all.json because the combined file exceeds Cloudflare Pages’ 25 MiB per-file limit.



77
78
79
# File 'lib/jpzip/client.rb', line 77

def lookup_all
  parallel_merge(0.upto(9).map(&:to_s)) { |p1| fetch_url(group_url(p1)) }
end

#lookup_group(prefix) ⇒ Hash{String => Jpzip::ZipcodeEntry}

LookupGroup fetches all entries under a 1-, 2-, or 3-digit prefix. A 2-digit prefix fans out into 10 parallel prefix-3 fetches.

Returns:

Raises:



60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/jpzip/client.rb', line 60

def lookup_group(prefix)
  prefix = prefix.to_s
  raise InvalidPrefixError, "jpzip: prefix must be 1-3 digits, got #{prefix.inspect}" unless PREFIX_REGEX.match?(prefix)

  case prefix.length
  when 3
    fetch_prefix_dict(prefix) || {}
  when 1
    fetch_url(group_url(prefix)) || {}
  when 2
    parallel_merge(0.upto(9).map { |i| "#{prefix}#{i}" }) { |p3| fetch_prefix_dict(p3) }
  end
end

#memory_cache_sizeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



151
152
153
# File 'lib/jpzip/client.rb', line 151

def memory_cache_size
  @mem.size
end

#metaObject

Meta returns the cached /meta.json. First call hits the network; later calls return the cached value until #refresh is invoked.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/jpzip/client.rb', line 83

def meta
  @meta_mu.synchronize do
    return @meta_cached if @meta_resolved
  end

  result = Http.get("#{@base_url}/meta.json", http_client: @http_client)

  @meta_mu.synchronize do
    if result.status == 404
      @meta_resolved = true
      @meta_cached = nil
      return nil
    end

    parsed = JSON.parse(result.body)
    m = Meta.from_hash(parsed)

    if m.spec_version != SPEC_VERSION && @on_spec_mismatch
      @on_spec_mismatch.call(SPEC_VERSION, m.spec_version)
    end

    if @known_version && @known_version != m.version
      @mem.clear
      @cache&.clear
    end

    @known_version = m.version
    @meta_cached = m
    @meta_resolved = true
    m
  end
end

#preload(scope) ⇒ Object

Preload pulls the requested scope into L1 (and L2 when configured). scope is either the string “all” or a 1-3 digit prefix.

Raises:



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/jpzip/client.rb', line 118

def preload(scope)
  scope = scope.to_s
  if scope == "all"
    dict = lookup_all
    buckets = Hash.new { |h, k| h[k] = {} }
    dict.each { |zip, entry| buckets[zip[0, 3]][zip] = entry }
    buckets.each do |p, b|
      url = prefix_url(p)
      @mem.set(url, b)
      write_l2(url, b)
    end
    return nil
  end

  raise InvalidPrefixError, "jpzip: prefix must be 1-3 digits, got #{scope.inspect}" unless PREFIX_REGEX.match?(scope)

  lookup_group(scope)
  nil
end

#refreshObject

Refresh wipes L1 (and L2 when configured) and forgets cached meta.



139
140
141
142
143
144
145
146
147
148
# File 'lib/jpzip/client.rb', line 139

def refresh
  @mem.clear
  @meta_mu.synchronize do
    @meta_cached = nil
    @meta_resolved = false
    @known_version = nil
  end
  @cache&.clear
  nil
end