feat(pkg): expose a public library facade
Another repository can import pkg/addrkit and use the parser, index and resolver without HTTP or SQLite. The package only re-exports domain types and constructors and performs no I/O.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// Package addrkit is the public library facade of the address normalizer.
|
||||
//
|
||||
// The service (cmd/addrkitd) and the CLI use the same domain packages through
|
||||
// this API. Another project in a different repository can import this package
|
||||
// directly instead of calling the HTTP service; nothing here does I/O, opens a
|
||||
// database or depends on the service configuration.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// parser := addrkit.NewParser(
|
||||
// addrkit.NewDictionary([]addrkit.City{
|
||||
// {Name: "Новосибирск", Aliases: []string{"нск"}},
|
||||
// }),
|
||||
// "",
|
||||
// )
|
||||
// parsed := parser.Parse("нск, Ленина 5")
|
||||
//
|
||||
// index := addrkit.NewIndex("2026-10-01", entries, addrkit.DefaultOptions())
|
||||
// resolver := addrkit.NewResolver(index, nil)
|
||||
// result := resolver.Resolve(context.Background(), "нск, Ленина 5")
|
||||
package addrkit
|
||||
|
||||
import (
|
||||
"git.totmin.ru/en2zmax/addrkit/internal/domain/address"
|
||||
"git.totmin.ru/en2zmax/addrkit/internal/domain/match"
|
||||
)
|
||||
|
||||
// Core value types.
|
||||
type (
|
||||
// City is one entry of the configurable city dictionary.
|
||||
City = address.City
|
||||
// ParsedAddress is the structured form of a free-form address.
|
||||
ParsedAddress = address.ParsedAddress
|
||||
// Entry is one canonical registry row.
|
||||
Entry = address.Entry
|
||||
// Status is the machine-readable outcome of a resolution.
|
||||
Status = address.Status
|
||||
)
|
||||
|
||||
// Resolution statuses.
|
||||
const (
|
||||
StatusFound = address.StatusFound
|
||||
StatusPartial = address.StatusPartial
|
||||
StatusAmbiguous = address.StatusAmbiguous
|
||||
StatusNotFound = address.StatusNotFound
|
||||
)
|
||||
|
||||
// Dictionary is the immutable vocabulary used by the parser.
|
||||
type Dictionary = address.Dictionary
|
||||
|
||||
// NewDictionary builds a dictionary with the built-in vocabulary and cities.
|
||||
func NewDictionary(cities []City) *Dictionary {
|
||||
return address.NewDictionary(cities)
|
||||
}
|
||||
|
||||
// Parser turns free-form address strings into structured addresses.
|
||||
type Parser = address.Parser
|
||||
|
||||
// NewParser builds a parser. defaultCity may be empty.
|
||||
func NewParser(dict *Dictionary, defaultCity string) *Parser {
|
||||
return address.NewParser(dict, defaultCity)
|
||||
}
|
||||
|
||||
// Parse uses the shared city-less parser.
|
||||
func Parse(raw string) ParsedAddress {
|
||||
return address.Parse(raw)
|
||||
}
|
||||
|
||||
// Matching and resolution.
|
||||
type (
|
||||
// Options tunes the index.
|
||||
Options = match.Options
|
||||
// Candidate is one plausible match.
|
||||
Candidate = match.Candidate
|
||||
// Result is the outcome of a resolution.
|
||||
Result = match.Result
|
||||
// Rewriter is the optional query rewriter (for example an LLM).
|
||||
Rewriter = match.Rewriter
|
||||
// Index is an immutable in-memory registry index.
|
||||
Index = match.Index
|
||||
// Resolver resolves parsed addresses against an index.
|
||||
Resolver = match.Resolver
|
||||
)
|
||||
|
||||
// ResolverOptions tunes optional resolver behaviour.
|
||||
type ResolverOptions struct {
|
||||
// Parser defaults to a city-less parser.
|
||||
Parser *Parser
|
||||
// OnRewriteError observes failures of the optional rewriter. The resolver
|
||||
// always keeps the deterministic result.
|
||||
OnRewriteError func(error)
|
||||
}
|
||||
|
||||
// DefaultOptions returns the conservative matcher defaults.
|
||||
func DefaultOptions() Options {
|
||||
return match.DefaultOptions()
|
||||
}
|
||||
|
||||
// NewIndex builds an index from canonical entries.
|
||||
func NewIndex(version string, entries []Entry, opts Options) *Index {
|
||||
return match.NewIndex(version, entries, opts)
|
||||
}
|
||||
|
||||
// NewResolver builds a resolver with the default parser.
|
||||
func NewResolver(index *Index, rewriter Rewriter) *Resolver {
|
||||
return match.NewResolver(index, rewriter)
|
||||
}
|
||||
|
||||
// NewResolverWithOptions builds a resolver with explicit options.
|
||||
func NewResolverWithOptions(index *Index, rewriter Rewriter, opts ResolverOptions) *Resolver {
|
||||
return match.NewResolverWithOptions(index, rewriter, match.ResolverOptions{
|
||||
Parser: opts.Parser,
|
||||
OnRewriteError: opts.OnRewriteError,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package addrkit_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.totmin.ru/en2zmax/addrkit/pkg/addrkit"
|
||||
)
|
||||
|
||||
// TestPublicAPI exercises the facade exactly the way another repository would:
|
||||
// parse a city-aware query and resolve it against an in-memory index.
|
||||
func TestPublicAPI(t *testing.T) {
|
||||
dict := addrkit.NewDictionary([]addrkit.City{
|
||||
{Name: "Новосибирск", Aliases: []string{"нск"}},
|
||||
{Name: "Бердск"},
|
||||
})
|
||||
parser := addrkit.NewParser(dict, "")
|
||||
|
||||
entries := make([]addrkit.Entry, 0, 2)
|
||||
for _, city := range []string{"Новосибирск", "Бердск"} {
|
||||
p := parser.ParseInCity("Ленина улица 5", city)
|
||||
entries = append(entries, addrkit.Entry{
|
||||
ID: city, Address: "Ленина улица 5",
|
||||
City: p.City, CityKey: p.CityKey,
|
||||
Ordinal: p.Ordinal, NameKey: p.NameKey, NameDisplay: p.NameDisplay,
|
||||
StreetType: p.StreetType, StreetKey: p.StreetKey,
|
||||
House: p.House,
|
||||
})
|
||||
}
|
||||
|
||||
resolver := addrkit.NewResolverWithOptions(
|
||||
addrkit.NewIndex("2026-10-01", entries, addrkit.DefaultOptions()),
|
||||
nil,
|
||||
addrkit.ResolverOptions{Parser: parser},
|
||||
)
|
||||
|
||||
res := resolver.ResolveInCity(context.Background(), "нск, Ленина 5", "")
|
||||
if res.Status != addrkit.StatusFound || res.City != "Новосибирск" {
|
||||
t.Fatalf("result = %+v", res)
|
||||
}
|
||||
|
||||
ambiguous := resolver.Resolve(context.Background(), "Ленина 5")
|
||||
if ambiguous.Status != addrkit.StatusAmbiguous {
|
||||
t.Fatalf("cityless result = %+v", ambiguous)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user