[#1259] Move pkg/util/locode to frostfs-locode-db

Removed pkg/util/locode package, added
git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode dependency.

Signed-off-by: George Bartolomey <george@bh4.ru>
This commit is contained in:
George Bartolomey 2024-07-22 11:12:41 +03:00
parent 1032075a21
commit 9c2c76ca32
Signed by: george.bartolomey
GPG key ID: 35BC54839D73BFAD
30 changed files with 11 additions and 2022 deletions

3
go.mod
View file

@ -7,6 +7,7 @@ require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20240530152826-2f6d3209e1d3
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0
git.frostfs.info/TrueCloudLab/frostfs-locode-db v0.4.1-0.20240710074952-65761deb5c0d
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240617140730-1a5886e776de
git.frostfs.info/TrueCloudLab/hrw v1.2.1
@ -26,7 +27,6 @@ require (
github.com/nspcc-dev/neo-go v0.106.0
github.com/olekukonko/tablewriter v0.0.5
github.com/panjf2000/ants/v2 v2.9.0
github.com/paulmach/orb v0.11.0
github.com/prometheus/client_golang v1.19.0
github.com/spf13/cast v1.6.0
github.com/spf13/cobra v1.8.1
@ -51,7 +51,6 @@ require (
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
go.mongodb.org/mongo-driver v1.13.1 // indirect
)
require (

39
go.sum
View file

@ -6,6 +6,8 @@ git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f
git.frostfs.info/TrueCloudLab/frostfs-contract v0.19.3-0.20240621131249-49e5270f673e/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
git.frostfs.info/TrueCloudLab/frostfs-locode-db v0.4.1-0.20240710074952-65761deb5c0d h1:uJ/wvuMdepbkaV8XMS5uN9B0FQWMep0CttSuDZiDhq0=
git.frostfs.info/TrueCloudLab/frostfs-locode-db v0.4.1-0.20240710074952-65761deb5c0d/go.mod h1:7ZZq8iguY7qFsXajdHGmZd2AW4QbucyrJwhbsRfOfek=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 h1:PaZ8GpnUoXxUoNsc1qp36bT2u7FU+neU4Jn9cl8AWqI=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65/go.mod h1:6aAX80dvJ3r5fjN9CzzPglRptoiPgIC9KFGGsUA+1Hw=
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20240617140730-1a5886e776de h1:OjsWY0jpGJV1t87XgwL/3PsDx7fJ6lfNMXtY8UhoUbM=
@ -81,7 +83,6 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-pkgz/expirable-cache/v3 v3.0.0 h1:u3/gcu3sabLYiTCevoRKv+WzjIn5oo7P8XtiXBeRDLw=
github.com/go-pkgz/expirable-cache/v3 v3.0.0/go.mod h1:2OQiDyEGQalYecLWmXprm3maPXeVb5/6/X7yRPYTzec=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@ -91,13 +92,11 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -129,20 +128,14 @@ github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.12.1 h1:NhWgum1efX1x58daOBGCFWcxtEhOhXKKl1HAPQUp03Q=
github.com/klauspost/reedsolomon v1.12.1/go.mod h1:nEi5Kjb6QqtbofI6s+cbG/j1da11c96IBYBSnVGtuBs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c=
@ -171,7 +164,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY=
github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU=
github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
@ -210,12 +202,8 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/panjf2000/ants/v2 v2.9.0 h1:SztCLkVxBRigbg+vt0S5QvF5vxAbxbKt09/YfAJ0tEo=
github.com/panjf2000/ants/v2 v2.9.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/paulmach/orb v0.11.0 h1:JfVXJUBeH9ifc/OrhBY0lL16QsmPgpCHMlqSSYhcgAA=
github.com/paulmach/orb v0.11.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -259,7 +247,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
@ -272,25 +259,14 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
@ -318,13 +294,11 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@ -333,12 +307,10 @@ golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -348,9 +320,7 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@ -393,7 +363,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
@ -401,9 +370,7 @@ golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
@ -427,11 +394,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

View file

@ -1,11 +1,11 @@
package innerring
import (
"git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode/db"
locodebolt "git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode/db/boltdb"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/netmap"
irlocode "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/netmap/nodevalidation/locode"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
locodebolt "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db/boltdb"
"github.com/spf13/viper"
)

View file

@ -4,7 +4,7 @@ import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
"git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
)

View file

@ -5,9 +5,9 @@ import (
"fmt"
"testing"
locodestd "git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode/db"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring/processors/netmap/nodevalidation/locode"
locodestd "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
"github.com/stretchr/testify/require"
)

View file

@ -1,8 +1,8 @@
package locode
import (
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
"git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-locode-db/pkg/locode/db"
)
// Record is an interface of read-only

View file

@ -1,193 +0,0 @@
package locodecolumn
import (
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
)
const (
minutesDigits = 2
hemisphereSymbols = 1
)
const (
latDegDigits = 2
lngDegDigits = 3
)
type coordinateCode struct {
degDigits int
value []uint8
}
// LongitudeCode represents the value of the longitude
// of the location conforming to UN/LOCODE specification.
type LongitudeCode coordinateCode
// LongitudeHemisphere represents the hemisphere of the earth
// // along the Greenwich meridian.
type LongitudeHemisphere [hemisphereSymbols]uint8
// LatitudeCode represents the value of the latitude
// of the location conforming to UN/LOCODE specification.
type LatitudeCode coordinateCode
// LatitudeHemisphere represents the hemisphere of the earth
// along the equator.
type LatitudeHemisphere [hemisphereSymbols]uint8
func coordinateFromString(s string, degDigits int, hemisphereAlphabet []uint8) (*coordinateCode, error) {
if len(s) != degDigits+minutesDigits+hemisphereSymbols {
return nil, locode.ErrInvalidString
}
for i := range s[:degDigits+minutesDigits] {
if !isDigit(s[i]) {
return nil, locode.ErrInvalidString
}
}
loop:
for _, sym := range s[degDigits+minutesDigits:] {
for j := range hemisphereAlphabet {
if hemisphereAlphabet[j] == uint8(sym) {
continue loop
}
}
return nil, locode.ErrInvalidString
}
return &coordinateCode{
degDigits: degDigits,
value: []uint8(s),
}, nil
}
// LongitudeFromString parses a string and returns the location's longitude.
func LongitudeFromString(s string) (*LongitudeCode, error) {
cc, err := coordinateFromString(s, lngDegDigits, []uint8{'W', 'E'})
if err != nil {
return nil, err
}
return (*LongitudeCode)(cc), nil
}
// LatitudeFromString parses a string and returns the location's latitude.
func LatitudeFromString(s string) (*LatitudeCode, error) {
cc, err := coordinateFromString(s, latDegDigits, []uint8{'N', 'S'})
if err != nil {
return nil, err
}
return (*LatitudeCode)(cc), nil
}
func (cc *coordinateCode) degrees() []uint8 {
return cc.value[:cc.degDigits]
}
// Degrees returns the longitude's degrees.
func (lc *LongitudeCode) Degrees() (l [lngDegDigits]uint8) {
copy(l[:], (*coordinateCode)(lc).degrees())
return
}
// Degrees returns the latitude's degrees.
func (lc *LatitudeCode) Degrees() (l [latDegDigits]uint8) {
copy(l[:], (*coordinateCode)(lc).degrees())
return
}
func (cc *coordinateCode) minutes() (mnt [minutesDigits]uint8) {
for i := 0; i < minutesDigits; i++ {
mnt[i] = cc.value[cc.degDigits+i]
}
return
}
// Minutes returns the longitude's minutes.
func (lc *LongitudeCode) Minutes() [minutesDigits]uint8 {
return (*coordinateCode)(lc).minutes()
}
// Minutes returns the latitude's minutes.
func (lc *LatitudeCode) Minutes() [minutesDigits]uint8 {
return (*coordinateCode)(lc).minutes()
}
// Hemisphere returns the longitude's hemisphere code.
func (lc *LongitudeCode) Hemisphere() LongitudeHemisphere {
return (*coordinateCode)(lc).hemisphere()
}
// Hemisphere returns the latitude's hemisphere code.
func (lc *LatitudeCode) Hemisphere() LatitudeHemisphere {
return (*coordinateCode)(lc).hemisphere()
}
func (cc *coordinateCode) hemisphere() (h [hemisphereSymbols]uint8) {
for i := 0; i < hemisphereSymbols; i++ {
h[i] = cc.value[cc.degDigits+minutesDigits+i]
}
return h
}
// North returns true for the northern hemisphere.
func (h LatitudeHemisphere) North() bool {
return h[0] == 'N'
}
// East returns true for the eastern hemisphere.
func (h LongitudeHemisphere) East() bool {
return h[0] == 'E'
}
// Coordinates represents the coordinates of the location from UN/LOCODE table.
type Coordinates struct {
lat *LatitudeCode
lng *LongitudeCode
}
// Latitude returns the location's latitude.
func (c *Coordinates) Latitude() *LatitudeCode {
return c.lat
}
// Longitude returns the location's longitude.
func (c *Coordinates) Longitude() *LongitudeCode {
return c.lng
}
// CoordinatesFromString parses a string and returns the location's coordinates.
func CoordinatesFromString(s string) (*Coordinates, error) {
if len(s) == 0 {
return nil, nil
}
strs := strings.Split(s, " ")
if len(strs) != 2 {
return nil, locode.ErrInvalidString
}
lat, err := LatitudeFromString(strs[0])
if err != nil {
return nil, fmt.Errorf("could not parse latitude: %w", err)
}
lng, err := LongitudeFromString(strs[1])
if err != nil {
return nil, fmt.Errorf("could not parse longitude: %w", err)
}
return &Coordinates{
lat: lat,
lng: lng,
}, nil
}

View file

@ -1,38 +0,0 @@
package locodecolumn
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
)
const countryCodeLen = 2
// CountryCode represents ISO 3166 alpha-2 Country Code.
type CountryCode [countryCodeLen]uint8
// Symbols returns digits of the country code.
func (cc *CountryCode) Symbols() [countryCodeLen]uint8 {
return *cc
}
// CountryCodeFromString parses a string and returns the country code.
func CountryCodeFromString(s string) (*CountryCode, error) {
if l := len(s); l != countryCodeLen {
return nil, fmt.Errorf("incorrect country code length: expect: %d, got: %d",
countryCodeLen,
l,
)
}
for i := range s {
if !isUpperAlpha(s[i]) {
return nil, locode.ErrInvalidString
}
}
cc := CountryCode{}
copy(cc[:], s)
return &cc, nil
}

View file

@ -1,38 +0,0 @@
package locodecolumn
import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
)
const locationCodeLen = 3
// LocationCode represents 3-character code for the location.
type LocationCode [locationCodeLen]uint8
// Symbols returns characters of the location code.
func (lc *LocationCode) Symbols() [locationCodeLen]uint8 {
return *lc
}
// LocationCodeFromString parses a string and returns the location code.
func LocationCodeFromString(s string) (*LocationCode, error) {
if l := len(s); l != locationCodeLen {
return nil, fmt.Errorf("incorrect location code length: expect: %d, got: %d",
locationCodeLen,
l,
)
}
for i := range s {
if !isUpperAlpha(s[i]) && !isDigit(s[i]) {
return nil, locode.ErrInvalidString
}
}
lc := LocationCode{}
copy(lc[:], s)
return &lc, nil
}

View file

@ -1,9 +0,0 @@
package locodecolumn
func isDigit(sym uint8) bool {
return sym >= '0' && sym <= '9'
}
func isUpperAlpha(sym uint8) bool {
return sym >= 'A' && sym <= 'Z'
}

View file

@ -1,194 +0,0 @@
package airportsdb
import (
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"strconv"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
)
const (
_ = iota - 1
_ // Airport ID
_ // Name
airportCity
airportCountry
airportIATA
_ // ICAO
airportLatitude
airportLongitude
_ // Altitude
_ // Timezone
_ // DST
_ // Tz database time zone
_ // Type
_ // Source
airportFldNum
)
type record struct {
city,
country,
iata,
lat,
lng string
}
// Get scans the records of the OpenFlights Airport to an in-memory table (once),
// and returns an entry that matches the passed UN/LOCODE record.
//
// Records are matched if they have the same country code and either
// same IATA code or same city name (location name in UN/LOCODE).
//
// Returns locodedb.ErrAirportNotFound if no entry matches.
func (db *DB) Get(locodeRecord locode.Record) (*locodedb.AirportRecord, error) {
if err := db.initAirports(); err != nil {
return nil, err
}
records := db.mAirports[locodeRecord.LOCODE.CountryCode()]
for i := range records {
if locodeRecord.LOCODE.LocationCode() != records[i].iata &&
locodeRecord.NameWoDiacritics != records[i].city {
continue
}
lat, err := strconv.ParseFloat(records[i].lat, 64)
if err != nil {
return nil, err
}
lng, err := strconv.ParseFloat(records[i].lng, 64)
if err != nil {
return nil, err
}
return &locodedb.AirportRecord{
CountryName: records[i].country,
Point: locodedb.NewPoint(lat, lng),
}, nil
}
return nil, locodedb.ErrAirportNotFound
}
const (
_ = iota - 1
countryName
countryISOCode
_ // dafif_code
countryFldNum
)
// CountryName scans the records of the OpenFlights Country table to an in-memory table (once),
// and returns the name of the country by code.
//
// Returns locodedb.ErrCountryNotFound if no entry matches.
func (db *DB) CountryName(code *locodedb.CountryCode) (name string, err error) {
if err = db.initCountries(); err != nil {
return
}
argCode := code.String()
for cName, cCode := range db.mCountries {
if cCode == argCode {
name = cName
break
}
}
if name == "" {
err = locodedb.ErrCountryNotFound
}
return
}
func (db *DB) initAirports() (err error) {
db.airportsOnce.Do(func() {
db.mAirports = make(map[string][]record)
if err = db.initCountries(); err != nil {
return
}
err = db.scanWords(db.airports, airportFldNum, func(words []string) error {
countryCode := db.mCountries[words[airportCountry]]
if countryCode != "" {
db.mAirports[countryCode] = append(db.mAirports[countryCode], record{
city: words[airportCity],
country: words[airportCountry],
iata: words[airportIATA],
lat: words[airportLatitude],
lng: words[airportLongitude],
})
}
return nil
})
})
return
}
func (db *DB) initCountries() (err error) {
db.countriesOnce.Do(func() {
db.mCountries = make(map[string]string)
err = db.scanWords(db.countries, countryFldNum, func(words []string) error {
db.mCountries[words[countryName]] = words[countryISOCode]
return nil
})
})
return
}
var errScanInt = errors.New("interrupt scan")
func (db *DB) scanWords(pm pathMode, num int, wordsHandler func([]string) error) error {
tableFile, err := os.OpenFile(pm.path, os.O_RDONLY, pm.mode)
if err != nil {
return err
}
defer tableFile.Close()
r := csv.NewReader(tableFile)
r.ReuseRecord = true
for {
words, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
} else if ln := len(words); ln != num {
return fmt.Errorf("unexpected number of words %d", ln)
}
if err := wordsHandler(words); err != nil {
if errors.Is(err, errScanInt) {
break
}
return err
}
}
return nil
}

View file

@ -1,83 +0,0 @@
package airportsdb
import (
"fmt"
"io/fs"
"sync"
)
// Prm groups the required parameters of the DB's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type Prm struct {
// Path to OpenFlights Airport csv table.
//
// Must not be empty.
AirportsPath string
// Path to OpenFlights Countries csv table.
//
// Must not be empty.
CountriesPath string
}
// DB is a descriptor of the OpenFlights database in csv format.
//
// For correct operation, DB must be created
// using the constructor (New) based on the required parameters
// and optional components. After successful creation,
// The DB is immediately ready to work through API.
type DB struct {
airports, countries pathMode
airportsOnce, countriesOnce sync.Once
mCountries map[string]string
mAirports map[string][]record
}
type pathMode struct {
path string
mode fs.FileMode
}
const invalidPrmValFmt = "invalid parameter %s (%T):%v"
func panicOnPrmValue(n string, v any) {
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
}
// New creates a new instance of the DB.
//
// Panics if at least one value of the parameters is invalid.
//
// The created DB does not require additional
// initialization and is completely ready for work.
func New(prm Prm, opts ...Option) *DB {
switch {
case prm.AirportsPath == "":
panicOnPrmValue("AirportsPath", prm.AirportsPath)
case prm.CountriesPath == "":
panicOnPrmValue("CountriesPath", prm.CountriesPath)
}
o := defaultOpts()
for i := range opts {
opts[i](o)
}
return &DB{
airports: pathMode{
path: prm.AirportsPath,
mode: o.airportMode,
},
countries: pathMode{
path: prm.CountriesPath,
mode: o.countryMode,
},
}
}

View file

@ -1,19 +0,0 @@
package airportsdb
import (
"io/fs"
)
// Option sets an optional parameter of DB.
type Option func(*options)
type options struct {
airportMode, countryMode fs.FileMode
}
func defaultOpts() *options {
return &options{
airportMode: fs.ModePerm, // 0777
countryMode: fs.ModePerm, // 0777
}
}

View file

@ -1,166 +0,0 @@
package locodebolt
import (
"encoding/json"
"errors"
"fmt"
"path/filepath"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
"go.etcd.io/bbolt"
)
// Open opens an underlying BoltDB instance.
//
// Timeout of BoltDB opening is 3s (only for Linux or Darwin).
//
// Opens BoltDB in read-only mode if DB is read-only.
func (db *DB) Open() error {
// copy-paste from metabase:
// consider universal Open/Close for BoltDB wrappers
err := util.MkdirAllX(filepath.Dir(db.path), db.mode)
if err != nil {
return fmt.Errorf("could not create dir for BoltDB: %w", err)
}
db.bolt, err = bbolt.Open(db.path, db.mode, db.boltOpts)
if err != nil {
return fmt.Errorf("could not open BoltDB: %w", err)
}
return nil
}
// Close closes an underlying BoltDB instance.
//
// Must not be called before successful Open call.
func (db *DB) Close() error {
return db.bolt.Close()
}
func countryBucketKey(cc *locodedb.CountryCode) ([]byte, error) {
return []byte(cc.String()), nil
}
func locationBucketKey(lc *locodedb.LocationCode) ([]byte, error) {
return []byte(lc.String()), nil
}
type recordJSON struct {
CountryName string
LocationName string
SubDivName string
SubDivCode string
Latitude float64
Longitude float64
Continent string
}
func recordValue(r locodedb.Record) ([]byte, error) {
p := r.GeoPoint()
rj := &recordJSON{
CountryName: r.CountryName(),
LocationName: r.LocationName(),
SubDivName: r.SubDivName(),
SubDivCode: r.SubDivCode(),
Latitude: p.Latitude(),
Longitude: p.Longitude(),
Continent: r.Continent().String(),
}
return json.Marshal(rj)
}
func recordFromValue(data []byte) (*locodedb.Record, error) {
rj := new(recordJSON)
if err := json.Unmarshal(data, rj); err != nil {
return nil, err
}
r := new(locodedb.Record)
r.SetCountryName(rj.CountryName)
r.SetLocationName(rj.LocationName)
r.SetSubDivName(rj.SubDivName)
r.SetSubDivCode(rj.SubDivCode)
r.SetGeoPoint(locodedb.NewPoint(rj.Latitude, rj.Longitude))
cont := locodedb.ContinentFromString(rj.Continent)
r.SetContinent(&cont)
return r, nil
}
// Put saves the record by key in an underlying BoltDB instance.
//
// Country code from the key is used for allocating the 1st level buckets.
// Records are stored in country buckets by the location code from the key.
// The records are stored in internal binary JSON format.
//
// Must not be called before successful Open call.
// Must not be called in read-only mode: behavior is undefined.
func (db *DB) Put(key locodedb.Key, rec locodedb.Record) error {
return db.bolt.Batch(func(tx *bbolt.Tx) error {
countryKey, err := countryBucketKey(key.CountryCode())
if err != nil {
return err
}
bktCountry, err := tx.CreateBucketIfNotExists(countryKey)
if err != nil {
return fmt.Errorf("could not create country bucket: %w", err)
}
locationKey, err := locationBucketKey(key.LocationCode())
if err != nil {
return err
}
cont, err := recordValue(rec)
if err != nil {
return err
}
return bktCountry.Put(locationKey, cont)
})
}
var errRecordNotFound = errors.New("record not found")
// Get reads the record by key from underlying BoltDB instance.
//
// Returns an error if no record is presented by key in DB.
//
// Must not be called before successful Open call.
func (db *DB) Get(key locodedb.Key) (rec *locodedb.Record, err error) {
err = db.bolt.View(func(tx *bbolt.Tx) error {
countryKey, err := countryBucketKey(key.CountryCode())
if err != nil {
return err
}
bktCountry := tx.Bucket(countryKey)
if bktCountry == nil {
return errRecordNotFound
}
locationKey, err := locationBucketKey(key.LocationCode())
if err != nil {
return err
}
data := bktCountry.Get(locationKey)
if data == nil {
return errRecordNotFound
}
rec, err = recordFromValue(data)
return err
})
return
}

View file

@ -1,73 +0,0 @@
package locodebolt
import (
"fmt"
"io/fs"
"go.etcd.io/bbolt"
)
// Prm groups the required parameters of the DB's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type Prm struct {
// Path to BoltDB file with FrostFS location database.
//
// Must not be empty.
Path string
}
// DB is a descriptor of the FrostFS BoltDB location database.
//
// For correct operation, DB must be created
// using the constructor (New) based on the required parameters
// and optional components.
//
// After successful creation,
// DB must be opened through Open call. After successful opening,
// DB is ready to work through API (until Close call).
//
// Upon completion of work with the DB, it must be closed
// by Close method.
type DB struct {
path string
mode fs.FileMode
boltOpts *bbolt.Options
bolt *bbolt.DB
}
const invalidPrmValFmt = "invalid parameter %s (%T):%v"
func panicOnPrmValue(n string, v any) {
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
}
// New creates a new instance of the DB.
//
// Panics if at least one value of the parameters is invalid.
//
// The created DB requires calling the Open method in order
// to initialize required resources.
func New(prm Prm, opts ...Option) *DB {
switch {
case prm.Path == "":
panicOnPrmValue("Path", prm.Path)
}
o := defaultOpts()
for i := range opts {
opts[i](o)
}
return &DB{
path: prm.Path,
mode: o.mode,
boltOpts: o.boltOpts,
}
}

View file

@ -1,37 +0,0 @@
package locodebolt
import (
"io/fs"
"os"
"time"
"go.etcd.io/bbolt"
)
// Option sets an optional parameter of DB.
type Option func(*options)
type options struct {
mode fs.FileMode
boltOpts *bbolt.Options
}
func defaultOpts() *options {
return &options{
mode: os.ModePerm, // 0777
boltOpts: &bbolt.Options{
Timeout: 3 * time.Second,
},
}
}
// ReadOnly enables read-only mode of the DB.
//
// Do not call DB.Put method on instances with
// this option: the behavior is undefined.
func ReadOnly() Option {
return func(o *options) {
o.boltOpts.ReadOnly = true
}
}

View file

@ -1,81 +0,0 @@
package locodedb
// Continent is an enumeration of Earth's continent.
type Continent uint8
const (
// ContinentUnknown is an undefined Continent value.
ContinentUnknown = iota
// ContinentEurope corresponds to Europe.
ContinentEurope
// ContinentAfrica corresponds to Africa.
ContinentAfrica
// ContinentNorthAmerica corresponds to North America.
ContinentNorthAmerica
// ContinentSouthAmerica corresponds to South America.
ContinentSouthAmerica
// ContinentAsia corresponds to Asia.
ContinentAsia
// ContinentAntarctica corresponds to Antarctica.
ContinentAntarctica
// ContinentOceania corresponds to Oceania.
ContinentOceania
)
// Is checks if c is the same continent as c2.
func (c *Continent) Is(c2 Continent) bool {
return *c == c2
}
func (c Continent) String() string {
switch c {
case ContinentUnknown:
fallthrough
default:
return "Unknown"
case ContinentEurope:
return "Europe"
case ContinentAfrica:
return "Africa"
case ContinentNorthAmerica:
return "North America"
case ContinentSouthAmerica:
return "South America"
case ContinentAsia:
return "Asia"
case ContinentAntarctica:
return "Antarctica"
case ContinentOceania:
return "Oceania"
}
}
// ContinentFromString returns Continent value
// corresponding to the passed string representation.
func ContinentFromString(str string) Continent {
switch str {
default:
return ContinentUnknown
case "Europe":
return ContinentEurope
case "Africa":
return ContinentAfrica
case "North America":
return ContinentNorthAmerica
case "South America":
return ContinentSouthAmerica
case "Asia":
return ContinentAsia
case "Antarctica":
return ContinentAntarctica
case "Oceania":
return ContinentOceania
}
}

View file

@ -1,98 +0,0 @@
package continentsdb
import (
"fmt"
"os"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geojson"
"github.com/paulmach/orb/planar"
)
const continentProperty = "Continent"
// PointContinent goes through all polygons and returns the continent
// in which the point is located.
//
// Returns locodedb.ContinentUnknown if no entry matches.
//
// All GeoJSON feature are parsed from file once and stored in memory.
func (db *DB) PointContinent(point *locodedb.Point) (*locodedb.Continent, error) {
var err error
db.once.Do(func() {
err = db.init()
})
if err != nil {
return nil, err
}
planarPoint := orb.Point{point.Longitude(), point.Latitude()}
var (
continent string
minDst float64
)
for _, feature := range db.features {
if multiPolygon, ok := feature.Geometry.(orb.MultiPolygon); ok {
if planar.MultiPolygonContains(multiPolygon, planarPoint) {
continent = feature.Properties.MustString(continentProperty)
break
}
} else if polygon, ok := feature.Geometry.(orb.Polygon); ok {
if planar.PolygonContains(polygon, planarPoint) {
continent = feature.Properties.MustString(continentProperty)
break
}
}
distance := planar.DistanceFrom(feature.Geometry, planarPoint)
if minDst == 0 || minDst > distance {
minDst = distance
continent = feature.Properties.MustString(continentProperty)
}
}
c := continentFromString(continent)
return &c, nil
}
func (db *DB) init() error {
data, err := os.ReadFile(db.path)
if err != nil {
return fmt.Errorf("could not read data file: %w", err)
}
features, err := geojson.UnmarshalFeatureCollection(data)
if err != nil {
return fmt.Errorf("could not unmarshal GeoJSON feature collection: %w", err)
}
db.features = features.Features
return nil
}
func continentFromString(c string) locodedb.Continent {
switch c {
default:
return locodedb.ContinentUnknown
case "Africa":
return locodedb.ContinentAfrica
case "Asia":
return locodedb.ContinentAsia
case "Europe":
return locodedb.ContinentEurope
case "North America":
return locodedb.ContinentNorthAmerica
case "South America":
return locodedb.ContinentSouthAmerica
case "Antarctica":
return locodedb.ContinentAntarctica
case "Australia", "Oceania":
return locodedb.ContinentOceania
}
}

View file

@ -1,63 +0,0 @@
package continentsdb
import (
"fmt"
"sync"
"github.com/paulmach/orb/geojson"
)
// Prm groups the required parameters of the DB's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type Prm struct {
// Path to polygons of Earth's continents in GeoJSON format.
//
// Must not be empty.
Path string
}
// DB is a descriptor of the Earth's polygons in GeoJSON format.
//
// For correct operation, DB must be created
// using the constructor (New) based on the required parameters
// and optional components. After successful creation,
// The DB is immediately ready to work through API.
type DB struct {
path string
once sync.Once
features []*geojson.Feature
}
const invalidPrmValFmt = "invalid parameter %s (%T):%v"
func panicOnPrmValue(n string, v any) {
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
}
// New creates a new instance of the DB.
//
// Panics if at least one value of the parameters is invalid.
//
// The created DB does not require additional
// initialization and is completely ready for work.
func New(prm Prm, opts ...Option) *DB {
switch {
case prm.Path == "":
panicOnPrmValue("Path", prm.Path)
}
o := defaultOpts()
for i := range opts {
opts[i](o)
}
return &DB{
path: prm.Path,
}
}

View file

@ -1,10 +0,0 @@
package continentsdb
// Option sets an optional parameter of DB.
type Option func(*options)
type options struct{}
func defaultOpts() *options {
return &options{}
}

View file

@ -1,32 +0,0 @@
package locodedb
import (
"fmt"
locodecolumn "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/column"
)
// CountryCode represents a country code for
// the storage in the FrostFS location database.
type CountryCode locodecolumn.CountryCode
// CountryCodeFromString parses a string UN/LOCODE country code
// and returns a CountryCode.
func CountryCodeFromString(s string) (*CountryCode, error) {
cc, err := locodecolumn.CountryCodeFromString(s)
if err != nil {
return nil, fmt.Errorf("could not parse country code: %w", err)
}
return CountryFromColumn(cc)
}
// CountryFromColumn converts a UN/LOCODE country code to a CountryCode.
func CountryFromColumn(cc *locodecolumn.CountryCode) (*CountryCode, error) {
return (*CountryCode)(cc), nil
}
func (c *CountryCode) String() string {
syms := (*locodecolumn.CountryCode)(c).Symbols()
return string(syms[:])
}

View file

@ -1,183 +0,0 @@
package locodedb
import (
"errors"
"fmt"
"runtime"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
"golang.org/x/sync/errgroup"
)
// SourceTable is an interface of the UN/LOCODE table.
type SourceTable interface {
// Must iterate over all entries of the table
// and pass next entry to the handler.
//
// Must return handler's errors directly.
IterateAll(func(locode.Record) error) error
}
// DB is an interface of FrostFS location database.
type DB interface {
// Must save the record by key in the database.
Put(Key, Record) error
// Must return the record by key from the database.
Get(Key) (*Record, error)
}
// AirportRecord represents the entry in FrostFS airport database.
type AirportRecord struct {
// Name of the country where airport is located.
CountryName string
// Geo point where airport is located.
Point *Point
}
// ErrAirportNotFound is returned by AirportRecord readers
// when the required airport is not found.
var ErrAirportNotFound = errors.New("airport not found")
// AirportDB is an interface of FrostFS airport database.
type AirportDB interface {
// Must return the record by UN/LOCODE table record.
//
// Must return ErrAirportNotFound if there is no
// related airport in the database.
Get(locode.Record) (*AirportRecord, error)
}
// ContinentsDB is an interface of FrostFS continent database.
type ContinentsDB interface {
// Must return continent of the geo point.
PointContinent(*Point) (*Continent, error)
}
var ErrSubDivNotFound = errors.New("subdivision not found")
var ErrCountryNotFound = errors.New("country not found")
// NamesDB is an interface of the FrostFS location namespace.
type NamesDB interface {
// Must resolve a country code to a country name.
//
// Must return ErrCountryNotFound if there is no
// country with the provided code.
CountryName(*CountryCode) (string, error)
// Must resolve (country code, subdivision code) to
// a subdivision name.
//
// Must return ErrSubDivNotFound if either country or
// subdivision is not presented in database.
SubDivName(*CountryCode, string) (string, error)
}
// FillDatabase generates the FrostFS location database based on the UN/LOCODE table.
func FillDatabase(table SourceTable, airports AirportDB, continents ContinentsDB, names NamesDB, db DB) error {
var errG errgroup.Group
// Pick some sane default, after this the performance stopped increasing.
errG.SetLimit(runtime.NumCPU() * 4)
_ = table.IterateAll(func(tableRecord locode.Record) error {
errG.Go(func() error {
return processTableRecord(tableRecord, airports, continents, names, db)
})
return nil
})
return errG.Wait()
}
func processTableRecord(tableRecord locode.Record, airports AirportDB, continents ContinentsDB, names NamesDB, db DB) error {
if tableRecord.LOCODE.LocationCode() == "" {
return nil
}
dbKey, err := NewKey(tableRecord.LOCODE)
if err != nil {
return err
}
dbRecord, err := NewRecord(tableRecord)
if err != nil {
if errors.Is(err, errParseCoordinates) {
return nil
}
return err
}
geoPoint := dbRecord.GeoPoint()
countryName := ""
if geoPoint == nil {
airportRecord, err := airports.Get(tableRecord)
if err != nil {
if errors.Is(err, ErrAirportNotFound) {
return nil
}
return err
}
geoPoint = airportRecord.Point
countryName = airportRecord.CountryName
}
dbRecord.SetGeoPoint(geoPoint)
if countryName == "" {
countryName, err = names.CountryName(dbKey.CountryCode())
if err != nil {
if errors.Is(err, ErrCountryNotFound) {
return nil
}
return err
}
}
dbRecord.SetCountryName(countryName)
if subDivCode := dbRecord.SubDivCode(); subDivCode != "" {
subDivName, err := names.SubDivName(dbKey.CountryCode(), subDivCode)
if err != nil {
if errors.Is(err, ErrSubDivNotFound) {
return nil
}
return err
}
dbRecord.SetSubDivName(subDivName)
}
continent, err := continents.PointContinent(geoPoint)
if err != nil {
return fmt.Errorf("could not calculate continent geo point: %w", err)
} else if continent.Is(ContinentUnknown) {
return nil
}
dbRecord.SetContinent(continent)
return db.Put(*dbKey, *dbRecord)
}
// LocodeRecord returns the record from the FrostFS location database
// corresponding to the string representation of UN/LOCODE.
func LocodeRecord(db DB, sLocode string) (*Record, error) {
lc, err := locode.FromString(sLocode)
if err != nil {
return nil, fmt.Errorf("could not parse locode: %w", err)
}
key, err := NewKey(*lc)
if err != nil {
return nil, err
}
return db.Get(*key)
}

View file

@ -1,32 +0,0 @@
package locodedb
import (
"fmt"
locodecolumn "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/column"
)
// LocationCode represents a location code for
// the storage in the FrostFS location database.
type LocationCode locodecolumn.LocationCode
// LocationCodeFromString parses a string UN/LOCODE location code
// and returns a LocationCode.
func LocationCodeFromString(s string) (*LocationCode, error) {
lc, err := locodecolumn.LocationCodeFromString(s)
if err != nil {
return nil, fmt.Errorf("could not parse location code: %w", err)
}
return LocationFromColumn(lc)
}
// LocationFromColumn converts a UN/LOCODE country code to a LocationCode.
func LocationFromColumn(cc *locodecolumn.LocationCode) (*LocationCode, error) {
return (*LocationCode)(cc), nil
}
func (l *LocationCode) String() string {
syms := (*locodecolumn.LocationCode)(l).Symbols()
return string(syms[:])
}

View file

@ -1,93 +0,0 @@
package locodedb
import (
"fmt"
"strconv"
locodecolumn "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/column"
)
// Point represents a 2D geographic point.
type Point struct {
lat, lng float64
}
// NewPoint creates, initializes and returns a new Point.
func NewPoint(lat, lng float64) *Point {
return &Point{
lat: lat,
lng: lng,
}
}
// Latitude returns the Point's latitude.
func (p Point) Latitude() float64 {
return p.lat
}
// Longitude returns the Point's longitude.
func (p Point) Longitude() float64 {
return p.lng
}
// PointFromCoordinates converts a UN/LOCODE coordinates to a Point.
func PointFromCoordinates(crd *locodecolumn.Coordinates) (*Point, error) {
if crd == nil {
return nil, nil
}
cLat := crd.Latitude()
cLatDeg := cLat.Degrees()
cLatMnt := cLat.Minutes()
lat, err := toDecimal(cLatDeg[:], cLatMnt[:])
if err != nil {
return nil, fmt.Errorf("could not parse latitude: %w", err)
}
if !cLat.Hemisphere().North() {
lat = -lat
}
cLng := crd.Longitude()
cLngDeg := cLng.Degrees()
cLngMnt := cLng.Minutes()
lng, err := toDecimal(cLngDeg[:], cLngMnt[:])
if err != nil {
return nil, fmt.Errorf("could not parse longitude: %w", err)
}
if !cLng.Hemisphere().East() {
lng = -lng
}
return &Point{
lat: lat,
lng: lng,
}, nil
}
func toDecimal(intRaw, minutesRaw []byte) (float64, error) {
integer, err := strconv.ParseFloat(string(intRaw), 64)
if err != nil {
return 0, fmt.Errorf("could not parse integer part: %w", err)
}
decimal, err := minutesToDegrees(minutesRaw)
if err != nil {
return 0, fmt.Errorf("could not parse decimal part: %w", err)
}
return integer + decimal, nil
}
// minutesToDegrees converts minutes to decimal part of a degree.
func minutesToDegrees(raw []byte) (float64, error) {
minutes, err := strconv.ParseFloat(string(raw), 64)
if err != nil {
return 0, err
}
return minutes / 60, nil
}

View file

@ -1,51 +0,0 @@
package locodedb
import (
"testing"
locodecolumn "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/column"
"github.com/stretchr/testify/require"
)
func TestPointFromCoordinates(t *testing.T) {
testCases := []struct {
latGot, longGot string
latWant, longWant float64
}{
{
latGot: "5915N",
longGot: "01806E",
latWant: 59.25,
longWant: 18.10,
},
{
latGot: "1000N",
longGot: "02030E",
latWant: 10.00,
longWant: 20.50,
},
{
latGot: "0145S",
longGot: "03512W",
latWant: -01.75,
longWant: -35.20,
},
}
var (
crd *locodecolumn.Coordinates
point *Point
err error
)
for _, test := range testCases {
crd, err = locodecolumn.CoordinatesFromString(test.latGot + " " + test.longGot)
require.NoError(t, err)
point, err = PointFromCoordinates(crd)
require.NoError(t, err)
require.Equal(t, test.latWant, point.Latitude())
require.Equal(t, test.longWant, point.Longitude())
}
}

View file

@ -1,140 +0,0 @@
package locodedb
import (
"errors"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodecolumn "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/column"
)
// Key represents the key in FrostFS location database.
type Key struct {
cc *CountryCode
lc *LocationCode
}
// NewKey calculates Key from LOCODE.
func NewKey(lc locode.LOCODE) (*Key, error) {
country, err := CountryCodeFromString(lc.CountryCode())
if err != nil {
return nil, fmt.Errorf("could not parse country: %w", err)
}
location, err := LocationCodeFromString(lc.LocationCode())
if err != nil {
return nil, fmt.Errorf("could not parse location: %w", err)
}
return &Key{
cc: country,
lc: location,
}, nil
}
// CountryCode returns the location's country code.
func (k *Key) CountryCode() *CountryCode {
return k.cc
}
// LocationCode returns the location code.
func (k *Key) LocationCode() *LocationCode {
return k.lc
}
// Record represents the entry in FrostFS location database.
type Record struct {
countryName string
locationName string
subDivName string
subDivCode string
p *Point
cont *Continent
}
var errParseCoordinates = errors.New("invalid coordinates")
// NewRecord calculates the Record from the UN/LOCODE table record.
func NewRecord(r locode.Record) (*Record, error) {
crd, err := locodecolumn.CoordinatesFromString(r.Coordinates)
if err != nil {
return nil, fmt.Errorf("%w: %v", errParseCoordinates, err)
}
point, err := PointFromCoordinates(crd)
if err != nil {
return nil, fmt.Errorf("could not parse geo point: %w", err)
}
return &Record{
locationName: r.NameWoDiacritics,
subDivCode: r.SubDiv,
p: point,
}, nil
}
// CountryName returns the country name.
func (r *Record) CountryName() string {
return r.countryName
}
// SetCountryName sets the country name.
func (r *Record) SetCountryName(name string) {
r.countryName = name
}
// LocationName returns the location name.
func (r *Record) LocationName() string {
return r.locationName
}
// SetLocationName sets the location name.
func (r *Record) SetLocationName(name string) {
r.locationName = name
}
// SubDivCode returns the subdivision code.
func (r *Record) SubDivCode() string {
return r.subDivCode
}
// SetSubDivCode sets the subdivision code.
func (r *Record) SetSubDivCode(name string) {
r.subDivCode = name
}
// SubDivName returns the subdivision name.
func (r *Record) SubDivName() string {
return r.subDivName
}
// SetSubDivName sets the subdivision name.
func (r *Record) SetSubDivName(name string) {
r.subDivName = name
}
// GeoPoint returns geo point of the location.
func (r *Record) GeoPoint() *Point {
return r.p
}
// SetGeoPoint sets geo point of the location.
func (r *Record) SetGeoPoint(p *Point) {
r.p = p
}
// Continent returns the location continent.
func (r *Record) Continent() *Continent {
return r.cont
}
// SetContinent sets the location continent.
func (r *Record) SetContinent(c *Continent) {
r.cont = c
}

View file

@ -1,83 +0,0 @@
package locode
import (
"errors"
"fmt"
"strings"
)
// LOCODE represents code from UN/LOCODE coding scheme.
type LOCODE [2]string
// Record represents a single record of the UN/LOCODE table.
type Record struct {
// Change Indicator.
Ch string
// Combination of a 2-character country code and a 3-character location code.
LOCODE LOCODE
// Name of the locations which has been allocated a UN/LOCODE.
Name string
// Names of the locations which have been allocated a UN/LOCODE without diacritic signs.
NameWoDiacritics string
// ISO 1-3 character alphabetic and/or numeric code for the administrative division of the country concerned.
SubDiv string
// 8-digit function classifier code for the location.
Function string
// Status of the entry by a 2-character code.
Status string
// Last date when the location was updated/entered.
Date string
// The IATA code for the location if different from location code in column LOCODE.
IATA string
// Geographical coordinates (latitude/longitude) of the location, if there is any.
Coordinates string
// Some general remarks regarding the UN/LOCODE in question.
Remarks string
}
// ErrInvalidString is the error of incorrect string format of the LOCODE.
var ErrInvalidString = errors.New("invalid string format in UN/Locode")
// FromString parses string and returns LOCODE.
//
// If string has incorrect format, ErrInvalidString returns.
func FromString(s string) (*LOCODE, error) {
const (
locationSeparator = " "
locodePartsNumber = 2
)
words := strings.Split(s, locationSeparator)
if ln := len(words); ln != locodePartsNumber {
return nil, fmt.Errorf(
"incorrect locode: it must consist of %d codes separated with a witespase, got: %d",
locodePartsNumber,
ln,
)
}
l := new(LOCODE)
copy(l[:], words)
return l, nil
}
// CountryCode returns a string representation of country code.
func (l *LOCODE) CountryCode() string {
return l[0]
}
// LocationCode returns a string representation of location code.
func (l *LOCODE) LocationCode() string {
return l[1]
}

View file

@ -1,156 +0,0 @@
package csvlocode
import (
"encoding/csv"
"errors"
"io"
"os"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode"
locodedb "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/locode/db"
)
var errInvalidRecord = errors.New("invalid table record")
// IterateAll scans a table record one-by-one, parses a UN/LOCODE record
// from it and passes it to f.
//
// Returns f's errors directly.
func (t *Table) IterateAll(f func(locode.Record) error) error {
const wordsPerRecord = 12
return t.scanWords(t.paths, wordsPerRecord, func(words []string) error {
lc, err := locode.FromString(strings.Join(words[1:3], " "))
if err != nil {
return err
}
record := locode.Record{
Ch: words[0],
LOCODE: *lc,
Name: words[3],
NameWoDiacritics: words[4],
SubDiv: words[5],
Function: words[6],
Status: words[7],
Date: words[8],
IATA: words[9],
Coordinates: words[10],
Remarks: words[11],
}
return f(record)
})
}
const (
_ = iota - 1
subDivCountry
subDivSubdivision
subDivName
_ // subDivLevel
subDivFldNum
)
type subDivKey struct {
countryCode,
subDivCode string
}
type subDivRecord struct {
name string
}
// SubDivName scans a table record to an in-memory table (once),
// and returns the subdivision name of the country and the subdivision codes match.
//
// Returns locodedb.ErrSubDivNotFound if no entry matches.
func (t *Table) SubDivName(countryCode *locodedb.CountryCode, code string) (string, error) {
if err := t.initSubDiv(); err != nil {
return "", err
}
rec, ok := t.mSubDiv[subDivKey{
countryCode: countryCode.String(),
subDivCode: code,
}]
if !ok {
return "", locodedb.ErrSubDivNotFound
}
return rec.name, nil
}
func (t *Table) initSubDiv() (err error) {
t.subDivOnce.Do(func() {
t.mSubDiv = make(map[subDivKey]subDivRecord)
err = t.scanWords([]string{t.subDivPath}, subDivFldNum, func(words []string) error {
t.mSubDiv[subDivKey{
countryCode: words[subDivCountry],
subDivCode: words[subDivSubdivision],
}] = subDivRecord{
name: words[subDivName],
}
return nil
})
})
return
}
var errScanInt = errors.New("interrupt scan")
func (t *Table) scanWords(paths []string, fpr int, wordsHandler func([]string) error) error {
var (
rdrs = make([]io.Reader, 0, len(t.paths))
closers = make([]io.Closer, 0, len(t.paths))
)
for i := range paths {
file, err := os.OpenFile(paths[i], os.O_RDONLY, t.mode)
if err != nil {
return err
}
rdrs = append(rdrs, file)
closers = append(closers, file)
}
defer func() {
for i := range closers {
_ = closers[i].Close()
}
}()
r := csv.NewReader(io.MultiReader(rdrs...))
r.ReuseRecord = true
r.FieldsPerRecord = fpr
for {
words, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
} else if len(words) != fpr {
return errInvalidRecord
}
if err := wordsHandler(words); err != nil {
if errors.Is(err, errScanInt) {
break
}
return err
}
}
return nil
}

View file

@ -1,28 +0,0 @@
package csvlocode
import (
"io/fs"
)
// Option sets an optional parameter of Table.
type Option func(*options)
type options struct {
mode fs.FileMode
extraPaths []string
}
func defaultOpts() *options {
return &options{
mode: 0o700,
}
}
// WithExtraPaths returns an option to add extra paths
// to UN/LOCODE tables in csv format.
func WithExtraPaths(ps ...string) Option {
return func(o *options) {
o.extraPaths = append(o.extraPaths, ps...)
}
}

View file

@ -1,75 +0,0 @@
package csvlocode
import (
"fmt"
"io/fs"
"sync"
)
// Prm groups the required parameters of the Table's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type Prm struct {
// Path to UN/LOCODE csv table.
//
// Must not be empty.
Path string
// Path to csv table of UN/LOCODE Subdivisions.
//
// Must not be empty.
SubDivPath string
}
// Table is a descriptor of the UN/LOCODE table in csv format.
//
// For correct operation, Table must be created
// using the constructor (New) based on the required parameters
// and optional components. After successful creation,
// The Table is immediately ready to work through API.
type Table struct {
paths []string
mode fs.FileMode
subDivPath string
subDivOnce sync.Once
mSubDiv map[subDivKey]subDivRecord
}
const invalidPrmValFmt = "invalid parameter %s (%T):%v"
func panicOnPrmValue(n string, v any) {
panic(fmt.Sprintf(invalidPrmValFmt, n, v, v))
}
// New creates a new instance of the Table.
//
// Panics if at least one value of the parameters is invalid.
//
// The created Table does not require additional
// initialization and is completely ready for work.
func New(prm Prm, opts ...Option) *Table {
switch {
case prm.Path == "":
panicOnPrmValue("Path", prm.Path)
case prm.SubDivPath == "":
panicOnPrmValue("SubDivPath", prm.SubDivPath)
}
o := defaultOpts()
for i := range opts {
opts[i](o)
}
return &Table{
paths: append(o.extraPaths, prm.Path),
mode: o.mode,
subDivPath: prm.SubDivPath,
}
}