pax_global_header00006660000000000000000000000064137411025110014505gustar00rootroot0000000000000052 comment=c12f88ecc944dfdc33c6c57f6726da908ae0acf7 toolbox-0.33.2/000077500000000000000000000000001374110251100132605ustar00rootroot00000000000000toolbox-0.33.2/.gitignore000066400000000000000000000000051374110251100152430ustar00rootroot00000000000000.ideatoolbox-0.33.2/.idea/000077500000000000000000000000001374110251100142405ustar00rootroot00000000000000toolbox-0.33.2/.idea/vcs.xml000066400000000000000000000002471374110251100155600ustar00rootroot00000000000000 toolbox-0.33.2/.travis.yml000077500000000000000000000000651374110251100153750ustar00rootroot00000000000000language: go go: - 1.8.1 script: - go test -v ./...toolbox-0.33.2/CHANGELOG.md000066400000000000000000000201411374110251100150670ustar00rootroot00000000000000## June 30 2020 - v.33.0 - Added sampler ## April 20 2020 - v.31.2 - Added ToLower, ToUpper in data.udf - Add AsNewLineDelimitedJSON in data.udf ## March 31 2020 - v.31.0 - Added bridge.ReadRecordedHttpTripsWithTemplate ## Fab 21 2020 - v.30.0 - Added CopyMap - Added OmitEmptyMapWriter ## Feb 7 2020 - v0.29.6 - Added data.udf.FormatTime with trunc parameter - Added cred.Config.Endpoint - Added http options to RouteToService ## October 9 - v0.29.0 - Added data.udf.Replace ## September 23 - v0.27.1 - Added Body matcher - Added url.Resource CustomKey ## July 29 2019 - v0.26.2 - Extended cred.Config - Patched gs.SetProvider ## July 26 2019 - v0.26.0 - Removed Base64 flag from kms - Added base64 auto detection - Added credConfig provider for gs - Added kms aws service - Add url.Resource: DownloadBase64 ## July 20 2019 - v0.25.2 - Update supported secret sequences ## July 18 2019 - v0.25.0 - Add Storage.UploadWithMode - Patched/streamlined SSH/SCP Upload - Streamline storage.Copy ## June 10 2019 - v0.24.0 - Added IsStructuredJSON - Updated IsCompleteJSON to be compatible with json.Valid - this is breaking change if you need check is input is structure json use IsStructuredJSON ## June 7 2019 - v0.23.8 - add Split udf (data/udf) ## June 1 2019 - v0.23.7 - patched TimeAt.Next ## May 22 2019 - v0.23.0 - Renamed StorageProvider to Registry ## May 21 2019 - v0.22.0 - Added AtTime schedule type - Minor patches ## May 13 2019 - v0.21.1 - Patched AsString ## May 7 2019 - v0.21.0 - Merge KMS helper ## May 6 2019 - v0.20.3 - Patched yaml detection ## April 17 2019 - v0.20.2 - Added cred.Config Email field - Patched zero value access error ## April 17 2019 - v0.20.0 - Added paraphrase ssh key support - Added AsFunctionParameters - Added string to []string conversion path ## April 14 2019 - v0.19.4 - Added StructFields sort type ## April 12 2019 - v0.19.3 - updated ReclassifyNotFoundIfMatched ## March 20 2019 - v0.19.1 - Added SplitTextStream util function ## March 20 2019 - v0.19.0 - Added gs storage with JSON credentials ## March 14 2019 - v0.19.0 - Added storage Tar archive support - Added IsDirectory helper method - Patched filesetinfo (function parameters names) - Added FileInfo to FileInfo to access relevant imports ## March 12 2019 - v0.18.4 - Patch json dependencies ## March 8 2019 - v0.18.3 - Patched storage scp file parsing ## March 6 2019 - v0.18.2 - Added AsStringMap udf ## March 1 2019 - v0.18.1 - Added custom converter Registry with RegisterConverter and GetConverter functions - Added TimeWindow util ## Feb 23 2019 - v0.18.0 - Added Fields and Ranger method on Compacted slice - Made compacted slice field public - Added Rand udf, patched udf mappings - Patched $AsString udf - Updated AsSlice to support Iterator ## Feb 13 2019 - v0.17.0 - Added $Select UDF ## Feb 12 2019 - v0.16.2 - patched *number nil pointer error - patched anonymous time struct conversion ## Feb 10 2019 - v0.16.0 - Added ConstValueProvider - Patched BuildTagMapping with anonymous explicit JSON field - Enhanced cast value provided with int32, int64, float32 - Patched NormalizeKVPairs ## Feb 3 2019 - v0.15.1 - Added Slice Intersect to collections ## Feb 3 2019 - v0.15.11 - Added Intersect utility method - Added Sum, Count, AsNumber Elapsed data/udf - Patched StructHelper - Patched Converter ## Feb 3 2019 - v0.14.0 - Added ToCaseFormat text util - Updated fileset reader to read interface method - Patched struct_helper panic - Optimized ReverseSlice ## Jan 31 2019 - v0.12.0 - Added storage/copy:ArchiveWithFilter - Added default project detection to google storage ## Jan 30 2019 - v0.11.1 - Patched new line delimited json decoding - Patched conversion slice to map error handling ## Jan 29 2019 - v0.11.0 - Remove storage/aws package, use storage/s3 instead - breakable change - Added google storage default http clinet (to run within GCE, or with GOOGLE_APPLICATION_CREDENTIALS) - Added google storage customization with GOOGLE_STORAGE_PROJECT env variable - Patched nil pointer check on fileset_info ## Jan 24 2019 - v0.10.3 - Update file set info fix IsPointerComponent in slice component type - Added recursive remove on file storage impl ## Jan 23 2019 - v0.10.1 - Added MaxIdleConnsPerHost http client option - Added TrimSpaces data/udf ## Jan 19 2019 - v0.10.0 - Added IsNumber helper function - Enhance Process Struct to handle unexported fields * Added UnexportedFieldHandler hadnler * Defined SetUnexportedFieldHandler function * Defined IgnoreUnexportedFields default handler - Enhanced GetStructMeta to handle unexported fields * Added StructMetaFilter * Defined DefaultStructMetaFilter default * Defined SetStructMetaFilter ## Jan 15 2019 - v0.9.0 - DownloadWithURL(URL string) (io.ReadCloser, error) ## Jan 13 2019 - v0.8.1 - Moved storage/aws to storage/s3 - Added lazy s3 bucket creation on upload - Added data/udf QueryUnescape ## Jan 13 2019 - v0.7.0 - Added ScanStructMethods method - Added TryDiscoverValueByKind method - Patched AsString UDF - Added Base64DecodeText udf - Added AnyJSONType for generic interface{} types - Added AccountID to cred/config - Patched []uint data substitution parsing ## Jan 8 2019 - v0.6.5 - Updated struct to map conversion with honoring tag name ## Jan 7 2019 - v0.6.5 - Patched non writable map data type mutation ## Jan 6 2019 - v0.6.4 - Added nested array mutation in data.Map - Patched url.resource yaml loading with array structure - Patched IndexOf - Minor patched ## Jan 3 2019 - v0.6.3 - Added NotFound error with helper functions - Updated handling not found on upload logic - Added Base64Encode, Base64Decode data udf - Added TerminatedSplitN text util function ## Jan 2 2019 - v0.6.2 - Added FollowRedirects option to http client ## Jan 1 2019 - v0.6.1 - Patched SortedIterator - Patched embedded non pointer struct conversion ## Dec 29 2018 - v0.6.0 - Added SortedRange, Iterator, SortedIterator to compacted slice ## Dec 28 2018 - v0.5.4 - Added QueryEscape udf - Updated handling udf with single quoted literals ## Dec 27 2018 - v0.5.3 - Added DecoderFactory method to url.Resource - Patched secret location with URL scheme ## Dec 26 2018 - v0.5.2 - Patched KV nested slice conversion - Patched handling unexported fields - Minor patches ## Dec 24 2018 - v0.5.1 - Patched KV conversion where value was nil - Updated secret service location lookup order - Minor patches ## Dec 18 2018 - v0.5.0 - NormalizeKVPairs - to converts slice of KV paris into a map, and map[interface{}]interface{} to map[string]interface{} - Moved stand expandable UDF from neatly project - Added data and data/udf documentation ## Dec 7 2018 - v0.4.1 - Enhanced UDF multi arguments calls - Added [] sub map key expression support - Patched name with sub references in Map.SetValue ## Dec 7 2018 - v0.4.0 - Added elapsed/remaining day helper functions: ElapsedDay, ElapsedToday, RemainingToday ## Dec 6 2018 - v0.3.8 - Patched udf arguments conversion glitch - Patched scp service with additional fallback for file scraping - Refactor data/map expression parser, Added basic arithmetic support - Added expansion of struct datatype, patched asEncodableValue ## Dec 3 2018 - v0.3.1 - Refactor data/map expression parser, Added basic arithmetic support - Refactor tokenizer matchers ## Nov 28 2018 - v0.2.4 - Patched ToInt, ToFloat conversion with nil pointer ## Nov 24 2018 - v0.2.3 - Added ToBoolean - Streamline ssh Session init ## Nov 19 2018 - v0.2.2 - Added error check for opening shell in ssh Session - Enhance SSH termination error ## Nov 8 2018 - v0.2.0 - Added TimeAt utility method for creating dynamically semantic based time. - Added IllegalTokenError, ExpectToken and ExpectTokenOptionallyFollowedBy parsing helpers - Added RemainingSequenceMatcher - Added SSH stdout buffering with listener frequency flush ## Oct 20 2018 - v0.1.1 - Added Replace method on data/map.go - Added path support to Delete method on on data/map ## Jul 1 2016 (Alpha) * Initial Release. toolbox-0.33.2/LICENSE.txt000066400000000000000000000264301374110251100151100ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2012-2016 Viant. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. toolbox-0.33.2/NOTICE.txt000066400000000000000000000002051374110251100147770ustar00rootroot00000000000000Viant, inc. Toolbox (toolbox) Copyright 2012-2016 Viant This product includes software developed at Viant (http://viantinc.com/)toolbox-0.33.2/README.md000066400000000000000000000362001374110251100145400ustar00rootroot00000000000000# Toolbox - go utility library [![GoReportCard](https://goreportcard.com/badge/github.com/viant/toolbox)](https://goreportcard.com/report/github.com/viant/toolbox) [![GoDoc](https://godoc.org/github.com/viant/toolbox?status.svg)](https://godoc.org/github.com/viant/toolbox) This library is compatible with Go 1.8+ Please refer to [`CHANGELOG.md`](CHANGELOG.md) if you encounter breaking changes. - [Motivation](#Motivation) - [Collection Utilities](#Collection-Utilities) - [Converter && Conversion Utilities](#Conversion-Utilities) - [Struct Utilities](#Struct-Utilities) - [Function Utilities](#Function-Utilities) - [Time Utilities](#TimeUtilities) - [Storage API](#storage) - [Data substitution](data/) - [Text Utilities](text/) - [ServiceRouter](#ServiceRouter) - [Decoder and Encoder](#DecoderandEncoder) - [Logger](#Logger) - [BatchLimiter](#BatchLimiter) - [AST Based FileSetInfo](#ast-based-filesetinfo) - [License](#License) - [Credits and Acknowledgements](#Credits-and-Acknowledgements) ## Motivation This library was developed as part of [Datastore Connectivity](https://github.com/viant/dsc/) and Testibility libraries: ([Assertly](https://github.com/viant/assertly), [Datastore testing](https://github.com/viant/dsunit/), [End to end testing](https://github.com/viant/endly/)) as a way to share utilities, and other abstractions that may be useful in other projects. ### Collection Utilities #### Iterator Example ```go slice := []string{"a", "z", "c"} iterator := toolbox.NewSliceIterator(slice) value := "" for iterator.HasNext() { iterator.Next(&value) ... } ``` #### Slice utilities The following methods work on **any slice type.** **ProcessSlice** Example ```go var aSlice interface{} toolbox.ProcessSlice(aSlice, func(item interface{}) bool { ... return true //to continue to next element return true }) ``` **ProcessSliceWithIndex** Example: ```go var aSlice interface{} toolbox.ProcessSlice(aSlice, func(index int, item interface{}) bool { ... return true //to continue to next element return true }) ``` **IndexSlice** Example: ```go type Foo struct{ id int name string } var aSlice = []Foo{ Foo{1, "A"}, Foo{2, "B"} } var indexedMap = make(map[int]Foo) toolbox.IndexSlice(aSlice, indexedMap, func(foo Foo) int { return foo.id }) ``` **CopySliceElements** Example: ```go source := []interface{}{ "abc", "def", "cyz", } var target = make([]string, 0) toolbox.CopySliceElements(source, &target) ``` **FilterSliceElements** Example: ```go source := []interface{}{ "abc", "def", "cyz","adc", } var target = make([]string, 0) toolbox.FilterSliceElements(source, func(item string) bool { return strings.HasPrefix(item, "a") //this matches any elements starting with a }, &target) ``` **HasSliceAnyElements** Example: ```go source := []interface{}{ "abc", "def", "cyz","adc", } toolbox.HasSliceAnyElements(source, "cyz") ``` **SliceToMap** Example: ```go var source = []Foo{ Foo{1, "A"}, Foo{2, "B"} } var target = make(map[int]string) toolbox.SliceToMap(source, target, func(foo Foo) int { return foo.id }, func(foo Foo) string { return foo.name }) ``` **TransformSlice** Example: ```go type Product struct{ vendor, name string } products := []Product{ Product{"Vendor1", "Product1"}, Product{"Vendor2", "Product2"}, Product{"Vendor1", "Product3"}, Product{"Vendor1", "Product4"}, } var vendors=make([]string, 0) toolbox.TransformSlice(products, &vendors, func(product Product) string { return product.vendor }) ``` #### Map utilities **ProcessMap** The following methods work on **any map type.** Example: ```go var aMap interface{} toolbox.ProcessMap(aMap, func(key, value interface{}) bool { ... return true //to continue to next element return true }) ``` **CopyMapEntries** Example: ```go type Foo struct{id int;name string} source := map[interface{}]interface{} { 1: Foo{1, "A"}, 2: Foo{2, "B"}, } var target = make (map[int]Foo) toolbox.CopyMapEntries(source, target) ``` **MapKeysToSlice** Example: ```go aMap := map[string]int { "abc":1, "efg":2, } var keys = make([]string, 0) toolbox.MapKeysToSlice(aMap, &keys) ``` **GroupSliceElements** Example: ```go type Product struct{vendor,name string} products := []Product{ Product{"Vendor1", "Product1"}, Product{"Vendor2", "Product2"}, Product{"Vendor1", "Product3"}, Product{"Vendor1", "Product4"}, } productsByVendor := make(map[string][]Product) toolbox.GroupSliceElements(products, productsByVendor, func(product Product) string { return product.vendor }) ``` **SliceToMultimap** ```go type Product struct { vendor, name string productId int } products := []Product{ Product{"Vendor1", "Product1", 1}, Product{"Vendor2", "Product2", 2}, Product{"Vendor1", "Product3", 3}, Product{"Vendor1", "Product4", 4}, } productsByVendor := make(map[string][]int) toolbox.SliceToMultimap(products, productsByVendor, func(product Product) string { return product.vendor }, func(product Product) int { return product.productId }) ``` ### Converter && Conversion Utilities Converter transforms, data between any compatible or incompatible data type including struct/basicType/map/slice/interface{} On top of that it supports custom tag to map field to target data type (i.e map) ```go myStruct := //some struct ... myMap := make(map[string]interface{}) converter := NewConverter(dateLayout, keyTag) err = converter.AssignConverted(&myMap, myStruct) err = converter.AssignConverted(myStruct, myMap) ``` ### Struct Utilities **ScanStructMethods** Scan struct methods ```go service := New() err = toolbox.ScanStructMethods(service, 1, func(method reflect.Method) error { fmt.Printf("%v\n", method.Name) return nil }) ``` **ProcessStruct** Scan struct fields ```go service := New() err = toolbox.ProcessStruct(service, func(field reflect.StructField, value reflect.Value) error { fmt.Print(field.Type.Name) return nil }) ``` ### Function Utilities ### Time Utilities **DateFormatToLayout** Java date format style to go date layout conversion. ```go dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z") timeValue, err := time.Parse(dateLaout, "2016-02-22 12:32:01 UTC") ``` **TimeAt** Provide dynamic semantic for creating time object ```go tomorrow, err = TimeAt("tomorrow")//tomorrow in local timezone timeInUTC, err := TimeAt("2 days ago in UTC") //or 2DayAgoInUTC yesterdayUTC, err := TimeAt("yesterdayInUTC")//yesterday in UTC hoursAhead, err := TimeAt("50 hours ahead") ``` **TimeDiff** Provide dynamic semantic for creating time object based on time dif ```go lastyear, _ := time.Parse(DateFormatToLayout("yyyy-MM-dd"), "2017-01-01") ts1, e := TimeDiff(lastyear, "50 hours earlier") ts2, e := TimeDiff(lastyear, "3 days before in Poland") ``` **DayElapsed** ```go t0, _ := time.Parse(DateFormatToLayout("yyyy-MM-dd hh:mm:ss"), "2017-01-01 12:00:00") dayElapsedInT0, err := ElapsedDay(t0) //0.5 ``` **ElapsedToday** ```go elapscedInLocalTz, err := ElapsedTodayInPct("") elapscedInUTC, err := ElapsedToday("UTC") ``` **RemainingToday** ```go elapscedInLocalTz, err := RemainingTodayInPct("") elapscedInUTC, err := RemainingToday("UTC") ``` **AtTime** ```go atTime := &AtTime{ WeekDay: "*", Hour: "*", Minute: "10,30", } //returns the nearest future time for xx:10 or xx:30 nextScheduleTime := atTime.Next(time.Now) ``` ## Storage [Storage API](storage/README.md) provides unified way of accessing local or remote storage system. This API has been deprecated, please consider using [Abstract Storage](https://github.com/viant/afs) **Example** ```go import ( "github.com/viant/toolbox/storage" _ "github.com/viant/toolbox/storage/gs" ) destinationURL := "gs://myBucket/set1/content.gz" destinationCredentialFile = "gs-secret.json" storageService, err := storage.NewServiceForURL(destinationURL, destinationCredentialFile) ``` ### Text utilities **ToCaseFormat** ```go formatted := toolbox.ToCaseFormat(text, toolbox.CaseLowerUnderscore, toolbox.CaseLowerCamel) ``` ### Tokenizer ### ServiceRouter This ServiceRouter provides simple WebService Endpoint abstractin and RESET Client utilities. Take as example of a ReverseService defined as follow ```go type ReverseService interface { Reverse(values []int) []int } type reverseService struct{} func (r *reverseService) Reverse(values []int) []int { var result = make([]int, 0) for i := len(values) - 1; i >= 0; i-- { result = append(result, values[i]) } return result } ``` In order to define Endpoint for this service, define a server, a router with the service routes; ```qo type Server struct { service ReverseService port string } func (s *Server) Start() { router := toolbox.NewServiceRouter( toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/reverse/{ids}", Handler: s.service.Reverse, Parameters: []string{"ids"}, }, toolbox.ServiceRouting{ HTTPMethod: "POST", URI: "/v1/reverse/", Handler: s.service.Reverse, Parameters: []string{"ids"}, }) http.HandleFunc("/v1/", func(writer http.ResponseWriter, reader *http.Request) { err := router.Route(writer, reader) if err != nil { response.WriteHeader(http.StatusInternalServerError) } }) fmt.Printf("Started test server on port %v\n", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } ``` **ServiceRouting** parameters define handler parameters that can be extracted from URI, QueryString, or from Post Body (json payload) In addition two special parameter names are supported: @httpRequest, @httpResponseWriter to pass in request, and response object respectively. The REST client utility invoking our reverse service may look as follow ```go var result = make([]int, 0) err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/reverse/1,7,3", nil, &result) //... err := toolbox.RouteToService("post", "http://127.0.0.1:8082/v1/reverse/", []int{1, 7, 3}, &result) ``` By default a service router uses reflection to call the matched routes handler, it is possible to avoid reflection overhead by providing the custom handler invoker. ```go var ReverseInvoker = func(serviceRouting *toolbox.ServiceRouting, request *http.Request, response http.ResponseWriter, uriParameters map[string]interface{}) error { var function = serviceRouting.Handler.(func(values []int) []int) idsParam := uriParameters["ids"] ids := idsParam.([]string) values := make([]int, 0) for _, item := range ids { values = append(values, toolbox.AsInt(item)) } var result = function(values) err := toolbox.WriteServiceRoutingResponse(response, request, serviceRouting, result) if err != nil { return err } return nil } //... router := toolbox.NewServiceRouter( toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/reverse/{ids}", Handler: s.service.Reverse, Parameters: []string{"ids"}, HandlerInvoker: ReverseInvoker, }) //... ``` ### Decoder and Encoder #### Decoder This library defines DecoderFactory interface to delegate decoder creation, This library comes with standard JSON and UnMarshaler (protobuf) factory implementation. Example ```go factory :=toolbox.NewJsonDecoderFactory() .... decoder := factory.Create(reader) foo := &Foo{} err = decoder.Decode(foo) marshalerFactory := toolbox.NewUnMarshalerDecoderFactory() decoder := marshalerFactory.Create(reader) foo := &Foo{} err = decoder.Decode(foo) ``` #### Encoder This library defines EncoderFactory interface to delegate encoder creation, This library comes with standard JSON and Marshaler (protobuf) factory implementation. Example ```go factory :=toolbox.NewJsonEncoderFactory() .... buffer := new(bytes.Buffer) decoder := factory.Create(buffer) err = decoder.Encode(foo) marshalerFactory := toolbox.NewMarshalerEncoderFactory() decoder := marshalerFactory.Create(buffer) err = decoder.Encode(foo) ``` ### Logger This library provides a file logger implementation that optimizes writes. Log messages are queues until max queue size or flush frequency are met. On top of that Ctrl-C also forces immediate log messages flush to disk. File template support java style time format to manage rotation on the file name level. ```go logger, err := toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyyMMdd-hhmm].log", QueueFlashCount: 250, MaxQueueSize: 500, FlushFrequencyInMs: 2000, MaxIddleTimeInSec: 1, }, toolbox.FileLoggerConfig{ LogType: "transaction", FileTemplate: "/tmp/transaction[yyyyMMdd-hhmm].log", QueueFlashCount: 250, MaxQueueSize: 500, FlushFrequencyInMs:2000, MaxIddleTimeInSec: 1, }, ) logger.Log(&toolbox.LogMessage{ MessageType: "test", Message: message }) logger.Log(&toolbox.LogMessage{ MessageType: "transaction", Message: message }) ``` ### BatchLimiter This library provides a batch limiter, that enables controling number of active go routines. ```go var tasks []*Task var batchSize = 4 limiter:= toolbox.NewBatchLimiter(batchSize, len(tasks)) for i, _ := range tasks { go func(task *Task) { limiter.Acquire() defer limiter.Done() task.Run(); }(tasks[i]) } limiter.Wait() ``` ### AST Based FileSetInfo ```go pkgPath := "" source := path.Join(pkgPath) filesetInfo, err :=toolbox.NewFileSetInfo(source) myType := fileSetInfo.Type("MyType") fields := myType.Fields() method := myType.Receivers ``` ## GoCover [![GoCover](https://gocover.io/github.com/viant/toolbox)](https://gocover.io/github.com/viant/toolbox) ## License The source code is made available under the terms of the Apache License, Version 2, as stated in the file `LICENSE`. Individual files may be made available under their own specific license, all compatible with Apache License, Version 2. Please see individual files for details. ## Credits and Acknowledgements **Library Author:** Adrian Witas **Contributors:** toolbox-0.33.2/batch_limiter.go000066400000000000000000000017471374110251100164260ustar00rootroot00000000000000package toolbox import "sync" //BatchLimiter represents a batch limiter type BatchLimiter struct { queue chan uint8 group *sync.WaitGroup Mutex *sync.RWMutex } //Acquire takes token form a channel, or wait if no more elements in a a channel func (r *BatchLimiter) Acquire() { <-r.queue } //Add adds element to wait group func (r *BatchLimiter) Add(delta int) { r.group.Add(delta) } //Done flags wait group as done, returns back a token to a channel func (r *BatchLimiter) Done() { r.group.Done() r.queue <- uint8(1) } //Wait wait on wait group func (r *BatchLimiter) Wait() { r.group.Wait() } //NewBatchLimiter creates a new batch limiter with batch size and total number of elements func NewBatchLimiter(batchSize, total int) *BatchLimiter { var queue = make(chan uint8, batchSize) for i := 0; i < batchSize; i++ { queue <- uint8(1) } result := &BatchLimiter{ queue: queue, group: &sync.WaitGroup{}, Mutex: &sync.RWMutex{}, } result.group.Add(total) return result } toolbox-0.33.2/batch_limiter_test.go000066400000000000000000000012031374110251100174500ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestBatchLimiter(t *testing.T) { var numbers = []int{1, 4, 6, 2, 5, 7, 5, 5, 7, 2, 3, 5, 7, 6} limiter := toolbox.NewBatchLimiter(4, len(numbers)) var sum int32 = 0 for _, n := range numbers { go func(n int32) { limiter.Acquire() defer limiter.Done() limiter.Mutex.Lock() defer limiter.Mutex.Unlock() //atomic.AddInt32(&sum, int32(n)) sum = sum + int32(n) }(int32(n)) } limiter.Wait() var expected int32 = 0 for _, n := range numbers { expected += int32(n) } assert.Equal(t, expected, sum) } toolbox-0.33.2/bridge/000077500000000000000000000000001374110251100145145ustar00rootroot00000000000000toolbox-0.33.2/bridge/byte_buffer_pool.go000066400000000000000000000024311374110251100203700ustar00rootroot00000000000000package bridge import ( "io" "net/http/httputil" ) type bytesBufferPool struct { channel chan []byte bufferSize int } func (p *bytesBufferPool) Get() (result []byte) { select { case result = <-p.channel: default: result = make([]byte, p.bufferSize) } return result } func (p *bytesBufferPool) Put(b []byte) { select { case p.channel <- b: default: //If the pool is full, discard the buffer. } } //NewBytesBufferPool returns new httputil.BufferPool pool. func NewBytesBufferPool(poolSize, bufferSize int) httputil.BufferPool { return &bytesBufferPool{ channel: make(chan []byte, poolSize), bufferSize: bufferSize, } } //CopyBuffer copies bytes from passed in source to destination with provided pool func CopyWithBufferPool(source io.Reader, destination io.Writer, pool httputil.BufferPool) (int64, error) { buf := pool.Get() defer pool.Put(buf) var written int64 for { bytesRead, readError := source.Read(buf) if bytesRead > 0 { bytesWritten, writeError := destination.Write(buf[:bytesRead]) if bytesWritten > 0 { written += int64(bytesWritten) } if writeError != nil { return written, writeError } if bytesRead != bytesWritten { return written, io.ErrShortWrite } } if readError != nil { return written, readError } } } toolbox-0.33.2/bridge/byte_buffer_pool_test.go000066400000000000000000000010341374110251100214250ustar00rootroot00000000000000package bridge_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestNewBytesBufferPool(t *testing.T) { pool := toolbox.NewBytesBufferPool(2, 1024) buf := pool.Get() //creates a new buffer pool is empty. assert.NotNil(t, buf) buf[1] = 0x2 pool.Put(buf) pool.Put([]byte{0x1}) pool.Put(buf) //get discarded { poolBuf := pool.Get() assert.Equal(t, uint8(0x2), poolBuf[1]) assert.Equal(t, 1024, len(poolBuf)) } { poolBuf := pool.Get() assert.Equal(t, 1, len(poolBuf)) } } toolbox-0.33.2/bridge/http_bridge.go000066400000000000000000000244241374110251100173440ustar00rootroot00000000000000package bridge import ( "bytes" "encoding/base64" "encoding/json" "fmt" "github.com/viant/toolbox" "io" "io/ioutil" "log" "net" "net/http" "net/http/httputil" "net/url" "os" "path" "sync" "time" "unicode" "unicode/utf8" ) //HttpBridgeConfig represent http bridge config type HttpBridgeEndpointConfig struct { Port string ReadTimeoutMs int WriteTimeoutMs int MaxHeaderBytes int } //HttpBridgeProxyRoute represent http proxy route type HttpBridgeProxyRoute struct { Pattern string TargetURL *url.URL ResponseModifier func(*http.Response) error Listener func(request *http.Request, response *http.Response) } //HttpBridgeProxyConfig represent proxy config type HttpBridgeProxyConfig struct { MaxIdleConnections int RequestTimeoutMs int KeepAliveTimeMs int TLSHandshakeTimeoutMs int BufferPoolSize int BufferSize int } //HttpBridgeConfig represents HttpBridgeConfig config type HttpBridgeConfig struct { Endpoint *HttpBridgeEndpointConfig Proxy *HttpBridgeProxyConfig Routes []*HttpBridgeProxyRoute } //ProxyHandlerFactory proxy handler factory type HttpBridgeProxyHandlerFactory func(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) //HttpBridge represents http bridge type HttpBridge struct { Config *HttpBridgeConfig Server *http.Server Handlers map[string]http.Handler } //ListenAndServe start http endpoint func (r *HttpBridge) ListenAndServe() error { return r.Server.ListenAndServe() } //ListenAndServe start http endpoint on secure port func (r *HttpBridge) ListenAndServeTLS(certFile, keyFile string) error { return r.Server.ListenAndServeTLS(certFile, keyFile) } //NewHttpBridge creates a new instance of NewHttpBridge func NewHttpBridge(config *HttpBridgeConfig, factory HttpBridgeProxyHandlerFactory) (*HttpBridge, error) { mux := http.NewServeMux() var handlers = make(map[string]http.Handler) for _, route := range config.Routes { handler, err := factory(config.Proxy, route) if err != nil { return nil, err } mux.Handle(route.Pattern, handler) handlers[route.Pattern] = handler } server := &http.Server{ Addr: ":" + config.Endpoint.Port, Handler: mux, ReadTimeout: time.Millisecond * time.Duration(config.Endpoint.ReadTimeoutMs), WriteTimeout: time.Millisecond * time.Duration(config.Endpoint.WriteTimeoutMs), MaxHeaderBytes: config.Endpoint.MaxHeaderBytes, } return &HttpBridge{ Server: server, Config: config, Handlers: handlers, }, nil } type handlerWrapper struct { Handler http.Handler } func (h *handlerWrapper) ServeHTTP(writer http.ResponseWriter, request *http.Request) { h.Handler.ServeHTTP(writer, request) } //NewProxyHandler creates a new proxy handler func NewProxyHandler(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) { roundTripper := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: time.Duration(proxyConfig.RequestTimeoutMs) * time.Millisecond, KeepAlive: time.Duration(proxyConfig.KeepAliveTimeMs) * time.Millisecond, }).Dial, TLSHandshakeTimeout: time.Duration(proxyConfig.TLSHandshakeTimeoutMs) * time.Millisecond, MaxIdleConnsPerHost: proxyConfig.MaxIdleConnections, } var director func(*http.Request) if route.TargetURL != nil { director = func(request *http.Request) { request.URL.Scheme = route.TargetURL.Scheme request.URL.Host = route.TargetURL.Host } } reverseProxy := &httputil.ReverseProxy{ Transport: roundTripper, BufferPool: toolbox.NewBytesBufferPool(proxyConfig.BufferPoolSize, proxyConfig.BufferSize), ModifyResponse: route.ResponseModifier, Director: director, } var handler http.Handler = &handlerWrapper{reverseProxy} return handler, nil } //HTTPTrip represents recorded round trip. type HttpTrip struct { responseWriter http.ResponseWriter Request *http.Request responseBody *bytes.Buffer responseStatusCode int } func (w *HttpTrip) Response() *http.Response { return &http.Response{ Request: w.Request, StatusCode: w.responseStatusCode, Header: w.responseWriter.Header(), Body: ioutil.NopCloser(bytes.NewReader(w.responseBody.Bytes())), } } func (w *HttpTrip) Write(b []byte) (int, error) { w.responseBody.Write(b) return w.responseWriter.Write(b) } func (w *HttpTrip) Header() http.Header { return w.responseWriter.Header() } func (w *HttpTrip) WriteHeader(status int) { w.responseStatusCode = status w.responseWriter.WriteHeader(status) } func (w *HttpTrip) Flush() { if flusher, ok := w.responseWriter.(http.Flusher); ok { flusher.Flush() } } func (w *HttpTrip) CloseNotify() <-chan bool { if closer, ok := w.responseWriter.(http.CloseNotifier); ok { return closer.CloseNotify() } return make(chan bool, 1) } //ListeningTripHandler represents endpoint recording handler type ListeningTripHandler struct { handler http.Handler pool httputil.BufferPool listener func(request *http.Request, response *http.Response) roundTripsMutex *sync.RWMutex } func (h *ListeningTripHandler) Notify(roundTrip *HttpTrip) { if h.listener != nil { h.listener(roundTrip.Request, roundTrip.Response()) } } //drainBody reads all of b to memory and then returns two equivalent (modified version from httputil) func (h ListeningTripHandler) drainBody(reader io.ReadCloser) (io.ReadCloser, io.ReadCloser, error) { if reader == http.NoBody { return http.NoBody, http.NoBody, nil } var buf = new(bytes.Buffer) toolbox.CopyWithBufferPool(reader, buf, h.pool) return ioutil.NopCloser(bytes.NewReader(buf.Bytes())), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil } func (h ListeningTripHandler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { var err error var originalRequest = request.WithContext(request.Context()) if request.ContentLength > 0 { request.Body, originalRequest.Body, err = h.drainBody(request.Body) if err != nil { log.Printf("failed to serve request :%v due to %v\n", request, err) return } } var recordedRoundTrip = &HttpTrip{ responseWriter: responseWriter, Request: originalRequest, responseBody: new(bytes.Buffer), } responseWriter = http.ResponseWriter(recordedRoundTrip) defer h.Notify(recordedRoundTrip) h.handler.ServeHTTP(responseWriter, request) } func NewListeningHandler(handler http.Handler, bufferPoolSize, bufferSize int, listener func(request *http.Request, response *http.Response)) *ListeningTripHandler { var result = &ListeningTripHandler{ handler: handler, listener: listener, pool: toolbox.NewBytesBufferPool(bufferPoolSize, bufferSize), roundTripsMutex: &sync.RWMutex{}, } return result } func NewProxyRecordingHandler(proxyConfig *HttpBridgeProxyConfig, route *HttpBridgeProxyRoute) (http.Handler, error) { handler, err := NewProxyHandler(proxyConfig, route) if err != nil { return nil, err } response := NewListeningHandler(handler, proxyConfig.BufferPoolSize, proxyConfig.BufferSize, route.Listener) return response, nil } func AsListeningTripHandler(handler http.Handler) *ListeningTripHandler { if result, ok := handler.(*ListeningTripHandler); ok { return result } return nil } //HttpRequest represents JSON serializable http request type HttpRequest struct { Method string `json:",omitempty"` URL string `json:",omitempty"` Header http.Header `json:",omitempty"` Body string `json:",omitempty"` ThinkTimeMs int `json:",omitempty"` } //NewHTTPRequest create a new instance of http request func NewHTTPRequest(method, url, body string, header http.Header) *HttpRequest { return &HttpRequest{ Method: method, URL: url, Body: body, Header: header, } } //HttpResponse represents JSON serializable http response type HttpResponse struct { Code int Header http.Header `json:",omitempty"` Body string `json:",omitempty"` } func ReaderAsText(reader io.Reader) string { if reader == nil { return "" } body, err := ioutil.ReadAll(reader) if err != nil { log.Fatal(err) } if isBinary(body) { buf := new(bytes.Buffer) encoder := base64.NewEncoder(base64.StdEncoding, buf) encoder.Write(body) encoder.Close() return fmt.Sprintf("base64:%v", string(buf.Bytes())) } else if len(body) > 0 { return fmt.Sprintf("text:%v", string(body)) } return "" } func isBinary(input []byte) bool { for i, w := 0, 0; i < len(input); i += w { runeValue, width := utf8.DecodeRune(input[i:]) if unicode.IsControl(runeValue) { return true } w = width } return false } func writeData(filename string, source interface{}, printStrOut bool) error { if toolbox.FileExists(filename) { os.Remove(filename) } logfile, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0666) if err != nil { log.Fatalf("error opening file: %v, %v", err, filename) } defer logfile.Close() buf, err := json.MarshalIndent(source, "", "\t") if err != nil { return err } _, err = logfile.Write(buf) if printStrOut { fmt.Printf("%v: %v\n", filename, string(buf)) } return err } //HttpFileRecorder returns http route listener that will record request response to the passed in directory func HttpFileRecorder(directory string, printStdOut bool) func(request *http.Request, response *http.Response) { tripCounter := 0 err := toolbox.CreateDirIfNotExist(directory) if err != nil { fmt.Printf("failed to create directory%v %v\n, ", err, directory) } return func(request *http.Request, response *http.Response) { var body string if request.Body != nil { body = ReaderAsText(request.Body) } httpRequest := &HttpRequest{ Method: request.Method, URL: request.URL.String(), Header: request.Header, Body: body, } err = writeData(path.Join(directory, fmt.Sprintf("%T-%v.json", *httpRequest, tripCounter)), httpRequest, printStdOut) if err != nil { fmt.Printf("failed to write request %v %v\n, ", err, request) } body = ReaderAsText(response.Body) request.Body = nil httpResponse := &HttpResponse{ Code: response.StatusCode, Header: response.Header, Body: body, } err = writeData(path.Join(directory, fmt.Sprintf("%T-%v.json", *httpResponse, tripCounter)), httpResponse, printStdOut) if err != nil { fmt.Printf("failed to write response %v %v\n, ", err, response) } tripCounter++ } } toolbox-0.33.2/bridge/http_bridge_recording_util.go000066400000000000000000000065161374110251100224370ustar00rootroot00000000000000package bridge import ( "bytes" "fmt" "github.com/pkg/errors" "github.com/viant/toolbox" "io/ioutil" "path" "strings" ) //RecordedHttpTrip represents a recorded http trip type RecordedHttpTrip struct { Request *HttpRequest Response *HttpResponse } //ReadRecordedHttpTripsWithPrefix scans provided directory for request, response pairs func ReadRecordedHttpTripsWithTemplate(directory string, requestTemplate, respTemplate string) ([]*RecordedHttpTrip, error) { if !strings.Contains(requestTemplate, "%") { return nil, errors.New("invalid request template: it could contains counter '%' expressions") } if !strings.Contains(respTemplate, "%") { return nil, errors.New("invalid response template: it could contains counter '%' expressions") } var result = make([]*RecordedHttpTrip, 0) var requestTemplatePath = path.Join(directory, requestTemplate) var responseTemplatePath = path.Join(directory, respTemplate) requests, err := readAll(requestTemplatePath, func() interface{} { return &HttpRequest{} }) if err != nil { return nil, err } responses, err := readAll(responseTemplatePath, func() interface{} { return &HttpResponse{} }) if err != nil { return nil, err } if len(requests) != len(responses) { return nil, fmt.Errorf("request and Response count does not match req:%v, resp:%v ", len(requests), len(responses)) } for i := 0; i < len(requests); i++ { var ok bool var trip = &RecordedHttpTrip{} trip.Request, ok = requests[i].(*HttpRequest) if !ok { return nil, fmt.Errorf("expected HttpRequest but had %T", requests[i]) } if i < len(responses) { trip.Response, ok = responses[i].(*HttpResponse) if !ok { return nil, fmt.Errorf("expected HttpRequest but had %T", responses[i]) } } result = append(result, trip) } return result, nil } //ReadRecordedHttpTrips scans provided directory for bridge.HttpRequest-%v.json and bridge.HttpResponse-%v.json template pairs func ReadRecordedHttpTrips(directory string) ([]*RecordedHttpTrip, error) { return ReadRecordedHttpTripsWithTemplate(directory, "bridge.HttpRequest-%v.json", "bridge.HttpResponse-%v.json") } func readAll(pathTemplate string, provider func() interface{}) ([]interface{}, error) { var result = make([]interface{}, 0) for i := 0; ; i++ { filename := fmt.Sprintf(pathTemplate, i) if !toolbox.FileExists(filename) { break } data, err := ioutil.ReadFile(filename) if err != nil { return nil, err } aStruct := provider() err = toolbox.NewJSONDecoderFactory().Create(bytes.NewReader(data)).Decode(aStruct) if err != nil { return nil, err } result = append(result, aStruct) } return result, nil } //StartRecordingBridge start recording bridge proxy func StartRecordingBridge(port string, outputDirectory string, routes ...*HttpBridgeProxyRoute) (*HttpBridge, error) { if len(routes) == 0 { routes = append(routes, &HttpBridgeProxyRoute{ Pattern: "/", }) } config := &HttpBridgeConfig{ Endpoint: &HttpBridgeEndpointConfig{ Port: port, }, Proxy: &HttpBridgeProxyConfig{ BufferPoolSize: 2, BufferSize: 8 * 1024, }, Routes: routes, } for _, route := range routes { route.Listener = HttpFileRecorder(outputDirectory, false) } httpBridge, err := NewHttpBridge(config, NewProxyRecordingHandler) if err != nil { return nil, err } go httpBridge.ListenAndServe() return httpBridge, nil } toolbox-0.33.2/bridge/http_bridge_test.go000066400000000000000000000121161374110251100203760ustar00rootroot00000000000000package bridge_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox/bridge" "io/ioutil" "log" "net" "net/http" "net/url" "strings" "testing" "time" ) func startTargetProxyTestEndpoint(port string, responses map[string]string) error { mux := http.NewServeMux() listener, err := net.Listen("tcp", ":"+port) if err != nil { return err } mux.HandleFunc("/", func(response http.ResponseWriter, request *http.Request) { if strings.ToUpper(request.Method) == "POST" { response.WriteHeader(http.StatusOK) data, err := ioutil.ReadAll(request.Body) if err != nil { log.Fatal(err) } response.Write(data) return } if body, ok := responses[request.URL.Path]; ok { response.WriteHeader(http.StatusOK) response.Write([]byte(body)) } else { response.WriteHeader(http.StatusNotFound) } }) go http.Serve(listener, mux) return nil } func startTestHttpBridge(port string, factory bridge.HttpBridgeProxyHandlerFactory, routes ...*bridge.HttpBridgeProxyRoute) (map[string]http.Handler, error) { config := &bridge.HttpBridgeConfig{ Endpoint: &bridge.HttpBridgeEndpointConfig{ Port: port, }, Proxy: &bridge.HttpBridgeProxyConfig{ BufferPoolSize: 2, BufferSize: 8 * 1024, }, Routes: routes, } httpBridge, err := bridge.NewHttpBridge(config, factory) if err != nil { return nil, err } go httpBridge.ListenAndServe() return httpBridge.Handlers, nil } func TestNewHttpBridge(t *testing.T) { for i := 0; i < 2; i++ { responses := make(map[string]string) responses[fmt.Sprintf("/test%v", i+1)] = fmt.Sprintf("Response1 from %v", 8088+i) err := startTargetProxyTestEndpoint(fmt.Sprintf("%v", 8088+i), responses) assert.Nil(t, err) } routes := make([]*bridge.HttpBridgeProxyRoute, 0) for i := 0; i < 2; i++ { targetURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%v/test%v", 8088+i, i+1)) assert.Nil(t, err) if err != nil { log.Fatal(err) } route := &bridge.HttpBridgeProxyRoute{ Pattern: fmt.Sprintf("/test%v", i+1), TargetURL: targetURL, } routes = append(routes, route) } startTestHttpBridge("8085", bridge.NewProxyHandler, routes...) time.Sleep(1 * time.Second) //Test direct responses for i := 0; i < 2; i++ { response, err := http.Get(fmt.Sprintf("http://127.0.0.1:%v/test%v", 8088+i, i+1)) assert.Nil(t, err) content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, fmt.Sprintf("Response1 from %v", 8088+i), string(content)) } //Test proxy responses for i := 0; i < 2; i++ { response, err := http.Get(fmt.Sprintf("http://127.0.0.1:8085/test%v", i+1)) assert.Nil(t, err) content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, fmt.Sprintf("Response1 from %v", 8088+i), string(content)) } } func TestNewHttpBridgeWithListeningHandler(t *testing.T) { basePort := 9098 for i := 0; i < 2; i++ { responses := make(map[string]string) responses[fmt.Sprintf("/test%v", i+1)] = fmt.Sprintf("Response1 from %v", basePort+i) err := startTargetProxyTestEndpoint(fmt.Sprintf("%v", basePort+i), responses) assert.Nil(t, err) } var responses = make([]*http.Response, 0) var listener = func(request *http.Request, response *http.Response) { responses = append(responses, response) } routes := make([]*bridge.HttpBridgeProxyRoute, 0) for i := 0; i < 2; i++ { targetURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%v/test%v", basePort+i, i+1)) assert.Nil(t, err) if err != nil { log.Fatal(err) } route := &bridge.HttpBridgeProxyRoute{ Pattern: fmt.Sprintf("/test%v", i+1), TargetURL: targetURL, Listener: listener, } routes = append(routes, route) } _, err := startTestHttpBridge("9085", bridge.NewProxyRecordingHandler, routes...) assert.Nil(t, err) time.Sleep(1 * time.Second) //Test direct responses for i := 0; i < 2; i++ { response, err := http.Get(fmt.Sprintf("http://127.0.0.1:%v/test%v", basePort+i, i+1)) assert.Nil(t, err) content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, fmt.Sprintf("Response1 from %v", basePort+i), string(content)) } //Test proxy responses for i := 0; i < 2; i++ { response, err := http.Get(fmt.Sprintf("http://127.0.0.1:9085/test%v", i+1)) assert.Nil(t, err) content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, fmt.Sprintf("Response1 from %v", basePort+i), string(content)) } { i := 0 response, err := http.Post(fmt.Sprintf("http://127.0.0.1:9085/test%v", i+1), "text/json", strings.NewReader("{\"a\":1}")) assert.Nil(t, err) content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, "{\"a\":1}", string(content)) } { assert.Equal(t, 3, len(responses)) time.Sleep(1 * time.Second) request := responses[0].Request assert.Equal(t, "/test1", request.URL.Path) response := responses[0] content, err := ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, "Response1 from 9098", string(content)) response = responses[2] content, err = ioutil.ReadAll(response.Body) assert.Nil(t, err) assert.Equal(t, "{\"a\":1}", string(content)) } } toolbox-0.33.2/byte_buffer_pool.go000066400000000000000000000024421374110251100171360ustar00rootroot00000000000000package toolbox import ( "io" "net/http/httputil" ) type bytesBufferPool struct { channel chan []byte bufferSize int } func (p *bytesBufferPool) Get() (result []byte) { select { case result = <-p.channel: default: result = make([]byte, p.bufferSize) } return result } func (p *bytesBufferPool) Put(b []byte) { select { case p.channel <- b: default: //If the pool is full, discard the buffer. } } //NewBytesBufferPool returns new httputil.BufferPool pool. func NewBytesBufferPool(poolSize, bufferSize int) httputil.BufferPool { return &bytesBufferPool{ channel: make(chan []byte, poolSize), bufferSize: bufferSize, } } //CopyWithBufferPool copies bytes from passed in source to destination with provided pool func CopyWithBufferPool(source io.Reader, destination io.Writer, pool httputil.BufferPool) (int64, error) { buf := pool.Get() defer pool.Put(buf) var written int64 for { bytesRead, readError := source.Read(buf) if bytesRead > 0 { bytesWritten, writeError := destination.Write(buf[:bytesRead]) if bytesWritten > 0 { written += int64(bytesWritten) } if writeError != nil { return written, writeError } if bytesRead != bytesWritten { return written, io.ErrShortWrite } } if readError != nil { return written, readError } } } toolbox-0.33.2/byte_buffer_pool_test.go000066400000000000000000000010351374110251100201720ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestNewBytesBufferPool(t *testing.T) { pool := toolbox.NewBytesBufferPool(2, 1024) buf := pool.Get() //creates a new buffer pool is empty. assert.NotNil(t, buf) buf[1] = 0x2 pool.Put(buf) pool.Put([]byte{0x1}) pool.Put(buf) //get discarded { poolBuf := pool.Get() assert.Equal(t, uint8(0x2), poolBuf[1]) assert.Equal(t, 1024, len(poolBuf)) } { poolBuf := pool.Get() assert.Equal(t, 1, len(poolBuf)) } } toolbox-0.33.2/collections.go000066400000000000000000000616111374110251100161320ustar00rootroot00000000000000package toolbox import ( "fmt" "github.com/pkg/errors" "reflect" "sort" "strings" ) //TrueValueProvider is a function that returns true, it takes one parameters which ignores, //This provider can be used to make map from slice like map[some type]bool var TrueValueProvider = func(ignore interface{}) bool { return true } //CopyStringValueProvider is a function that returns passed in string //This provider can be used to make map from slice like map[string]some type var CopyStringValueProvider = func(source string) string { return source } //ReverseSlice reverse a slice func ReverseSlice(source interface{}) { if source == nil { return } var j = 0 switch slice := source.(type) { case []byte: var sliceLen = len(slice) if sliceLen <= 1 { return } for i := sliceLen - 1; i >= (sliceLen / 2); i-- { item := slice[i] slice[i] = slice[j] slice[j] = item j++ } return case []interface{}: var sliceLen = len(slice) if sliceLen <= 1 { return } for i := sliceLen - 1; i >= (sliceLen / 2); i-- { item := slice[i] slice[i] = slice[j] slice[j] = item j++ } return case []string: var sliceLen = len(slice) if sliceLen <= 1 { return } for i := sliceLen - 1; i >= (sliceLen / 2); i-- { item := slice[i] slice[i] = slice[j] slice[j] = item j++ } return } sliceValue := reflect.ValueOf(source) if sliceValue.IsNil() || !sliceValue.IsValid() { return } if sliceValue.Kind() == reflect.Ptr { sliceValue = sliceValue.Elem() } var sliceLen = sliceValue.Len() if sliceLen <= 1 { return } for i := sliceLen - 1; i >= (sliceLen / 2); i-- { indexItem := sliceValue.Index(i) indexItemValue := indexItem.Elem() if indexItem.Kind() == reflect.Ptr { sliceValue.Index(i).Set(sliceValue.Index(j).Elem().Addr()) sliceValue.Index(j).Set(indexItemValue.Addr()) } else { sliceValue.Index(i).Set(sliceValue.Index(j).Elem()) sliceValue.Index(j).Set(indexItemValue) } j++ } } //ProcessSlice iterates over any slice, it calls handler with each element unless handler returns false, func ProcessSlice(slice interface{}, handler func(item interface{}) bool) { //The common cases with reflection for speed if aSlice, ok := slice.([]interface{}); ok { for _, item := range aSlice { if !handler(item) { break } } return } if aSlice, ok := slice.([]map[string]interface{}); ok { for _, item := range aSlice { if !handler(item) { break } } return } //The common cases with reflection for speed if aSlice, ok := slice.([]string); ok { for _, item := range aSlice { if !handler(item) { break } } return } //The common cases with reflection for speed if aSlice, ok := slice.([]int); ok { for _, item := range aSlice { if !handler(item) { break } } return } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) for i := 0; i < sliceValue.Len(); i++ { if !handler(sliceValue.Index(i).Interface()) { break } } } //ProcessSliceWithIndex iterates over any slice, it calls handler with every index and item unless handler returns false func ProcessSliceWithIndex(slice interface{}, handler func(index int, item interface{}) bool) { if aSlice, ok := slice.([]interface{}); ok { for i, item := range aSlice { if !handler(i, item) { break } } return } if aSlice, ok := slice.([]string); ok { for i, item := range aSlice { if !handler(i, item) { break } } return } if aSlice, ok := slice.([]int); ok { for i, item := range aSlice { if !handler(i, item) { break } } return } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) for i := 0; i < sliceValue.Len(); i++ { if !handler(i, sliceValue.Index(i).Interface()) { break } } } //AsSlice converts underlying slice or Ranger as []interface{} func AsSlice(sourceSlice interface{}) []interface{} { var result []interface{} ranger, ok := sourceSlice.(Ranger) if ok { result = []interface{}{} _ = ranger.Range(func(item interface{}) (bool, error) { result = append(result, item) return true, nil }) return result } iterator, ok := sourceSlice.(Iterator) if ok { if iterator.HasNext() { var item interface{} if err := iterator.Next(&item); err == nil { result = append(result, item) } } } result, ok = sourceSlice.([]interface{}) if ok { return result } if resultPointer, ok := sourceSlice.(*[]interface{}); ok { return *resultPointer } result = make([]interface{}, 0) CopySliceElements(sourceSlice, &result) return result } //IndexSlice reads passed in slice and applies function that takes a slice item as argument to return a key value. //passed in resulting map needs to match key type return by a key function, and accept slice item type as argument. func IndexSlice(slice, resultingMap, keyFunction interface{}) { mapValue := DiscoverValueByKind(resultingMap, reflect.Map) ProcessSlice(slice, func(item interface{}) bool { result := CallFunction(keyFunction, item) mapValue.SetMapIndex(reflect.ValueOf(result[0]), reflect.ValueOf(item)) return true }) } //CopySliceElements appends elements from source slice into target //This function comes handy if you want to copy from generic []interface{} slice to more specific slice like []string, if source slice element are of the same time func CopySliceElements(sourceSlice, targetSlicePointer interface{}) { if aTargetSlicePointer, ok := targetSlicePointer.(*[]interface{}); ok { ProcessSlice(sourceSlice, func(item interface{}) bool { *(aTargetSlicePointer) = append(*aTargetSlicePointer, item) return true }) return } if aTargetSlicePointer, ok := targetSlicePointer.(*[]string); ok { ProcessSlice(sourceSlice, func(item interface{}) bool { *(aTargetSlicePointer) = append(*aTargetSlicePointer, AsString(item)) return true }) return } AssertPointerKind(targetSlicePointer, reflect.Slice, "targetSlicePointer") sliceValue := reflect.ValueOf(targetSlicePointer).Elem() ProcessSlice(sourceSlice, func(item interface{}) bool { sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(item))) return true }) } //TransformSlice appends transformed elements from source slice into target, transformer take as argument item of source slice and return value of target slice. func TransformSlice(sourceSlice, targetSlicePointer, transformer interface{}) { AssertPointerKind(targetSlicePointer, reflect.Slice, "targetSlicePointer") sliceValue := reflect.ValueOf(targetSlicePointer).Elem() ProcessSlice(sourceSlice, func(item interface{}) bool { result := CallFunction(transformer, item) sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(result[0]))) return true }) } //FilterSliceElements copies elements from sourceSlice to targetSlice if predicate function returns true. Predicate function needs to accept source slice element type and return true. func FilterSliceElements(sourceSlice interface{}, predicate interface{}, targetSlicePointer interface{}) { //The most common case witout reflection if aTargetSlicePointer, ok := targetSlicePointer.(*[]string); ok { aPredicate, ok := predicate.(func(item string) bool) if !ok { panic("Invalid predicate") } ProcessSlice(sourceSlice, func(item interface{}) bool { if aPredicate(AsString(item)) { *(aTargetSlicePointer) = append(*aTargetSlicePointer, AsString(item)) } return true }) return } AssertPointerKind(targetSlicePointer, reflect.Slice, "targetSlicePointer") slicePointerValue := reflect.ValueOf(targetSlicePointer).Elem() ProcessSlice(sourceSlice, func(item interface{}) bool { result := CallFunction(predicate, item) if AsBoolean(result[0]) { slicePointerValue.Set(reflect.Append(slicePointerValue, reflect.ValueOf(item))) } return true }) } //HasSliceAnyElements checks if sourceSlice has any of passed in elements. This method iterates through elements till if finds the first match. func HasSliceAnyElements(sourceSlice interface{}, elements ...interface{}) (result bool) { ProcessSlice(sourceSlice, func(item interface{}) bool { for _, element := range elements { if item == element { result = true return false } } return true }) return result } //SliceToMap reads passed in slice to to apply the key and value function for each item. Result of these calls is placed in the resulting map. func SliceToMap(sourceSlice, targetMap, keyFunction, valueFunction interface{}) { //optimized case if stringBoolMap, ok := targetMap.(map[string]bool); ok { if stringSlice, ok := sourceSlice.([]string); ok { if valueFunction, ok := keyFunction.(func(string) bool); ok { if keyFunction, ok := keyFunction.(func(string) string); ok { for _, item := range stringSlice { stringBoolMap[keyFunction(item)] = valueFunction(item) } return } } } } mapValue := DiscoverValueByKind(targetMap, reflect.Map) ProcessSlice(sourceSlice, func(item interface{}) bool { key := CallFunction(keyFunction, item) value := CallFunction(valueFunction, item) mapValue.SetMapIndex(reflect.ValueOf(key[0]), reflect.ValueOf(value[0])) return true }) } //GroupSliceElements reads source slice and transfer all values returned by keyFunction to a slice in target map. func GroupSliceElements(sourceSlice, targetMap, keyFunction interface{}) { mapValue := DiscoverValueByKind(targetMap, reflect.Map) mapValueType := mapValue.Type().Elem() ProcessSlice(sourceSlice, func(item interface{}) bool { result := CallFunction(keyFunction, item) keyValue := reflect.ValueOf(result[0]) sliceForThisKey := mapValue.MapIndex(keyValue) if !sliceForThisKey.IsValid() { sliceForThisKeyPoiner := reflect.New(mapValueType) sliceForThisKey = sliceForThisKeyPoiner.Elem() mapValue.SetMapIndex(keyValue, sliceForThisKey) } mapValue.SetMapIndex(keyValue, reflect.Append(sliceForThisKey, reflect.ValueOf(item))) return true }) } //SliceToMultimap reads source slice and transfer all values by valueFunction and returned by keyFunction to a slice in target map. //Key and value function result type need to agree with target map type. func SliceToMultimap(sourceSlice, targetMap, keyFunction, valueFunction interface{}) { mapValue := DiscoverValueByKind(targetMap, reflect.Map) mapValueType := mapValue.Type().Elem() ProcessSlice(sourceSlice, func(item interface{}) bool { keyResult := CallFunction(keyFunction, item) keyValue := reflect.ValueOf(keyResult[0]) valueResult := CallFunction(valueFunction, item) value := reflect.ValueOf(valueResult[0]) sliceForThisKey := mapValue.MapIndex(keyValue) if !sliceForThisKey.IsValid() { sliceForThisKeyPoiner := reflect.New(mapValueType) sliceForThisKey = sliceForThisKeyPoiner.Elem() mapValue.SetMapIndex(keyValue, sliceForThisKey) } mapValue.SetMapIndex(keyValue, reflect.Append(sliceForThisKey, value)) return true }) } //SetSliceValue sets value at slice index func SetSliceValue(slice interface{}, index int, value interface{}) { if aSlice, ok := slice.([]string); ok { aSlice[index] = AsString(value) return } if aSlice, ok := slice.([]interface{}); ok { aSlice[index] = value return } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) sliceValue.Index(index).Set(reflect.ValueOf(value)) } //GetSliceValue gets value from passed in index func GetSliceValue(slice interface{}, index int) interface{} { if aSlice, ok := slice.([]string); ok { return aSlice[index] } if aSlice, ok := slice.([]interface{}); ok { return aSlice[index] } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) return sliceValue.Index(index).Interface() } var errSliceDoesNotHoldKeyValuePairs = errors.New("unable process map, not key value pairs") //ProcessMap iterates over any map, it calls handler with every key, value pair unless handler returns false. func ProcessMap(source interface{}, handler func(key, value interface{}) bool) error { switch aSlice := source.(type) { case map[string]string: for key, value := range aSlice { if !handler(key, value) { break } } return nil case map[string]interface{}: for key, value := range aSlice { if !handler(key, value) { break } } return nil case map[string]bool: for key, value := range aSlice { if !handler(key, value) { break } } return nil case map[string]int: for key, value := range aSlice { if !handler(key, value) { break } } return nil case map[interface{}]interface{}: for key, value := range aSlice { if !handler(key, value) { break } } return nil case map[int]interface{}: for key, value := range aSlice { if !handler(key, value) { break } } return nil } if IsSlice(source) { var err error var entryMap map[string]interface{} ProcessSlice(source, func(item interface{}) bool { entryMap, err = ToMap(item) if err != nil { return false } var key, value interface{} key, value, err = entryMapToKeyValue(entryMap) if err != nil { return false } return handler(key, value) }) if err != nil { return errSliceDoesNotHoldKeyValuePairs } return nil } if !IsMap(source) { return errSliceDoesNotHoldKeyValuePairs } mapValue := DiscoverValueByKind(reflect.ValueOf(source), reflect.Map) for _, key := range mapValue.MapKeys() { value := mapValue.MapIndex(key) if !handler(key.Interface(), value.Interface()) { break } } return nil } //ToMap converts underlying map/struct/[]KV as map[string]interface{} func ToMap(source interface{}) (map[string]interface{}, error) { if source == nil { return nil, nil } var result map[string]interface{} switch candidate := source.(type) { case map[string]interface{}: return candidate, nil case *map[string]interface{}: return *candidate, nil case map[interface{}]interface{}: result = make(map[string]interface{}) for k, v := range candidate { result[AsString(k)] = v } return result, nil } if IsStruct(source) { var result = make(map[string]interface{}) if err := DefaultConverter.AssignConverted(&result, source); err != nil { return nil, err } return result, nil } else if IsSlice(source) { var result = make(map[string]interface{}) if err := DefaultConverter.AssignConverted(&result, source); err != nil { return nil, err } return result, nil } sourceMapValue := reflect.ValueOf(source) mapType := reflect.TypeOf(result) if sourceMapValue.Type().AssignableTo(mapType) { result, ok := sourceMapValue.Convert(mapType).Interface().(map[string]interface{}) if !ok { return nil, fmt.Errorf("unable to convert: %T to %T", source, map[string]interface{}{}) } return result, nil } result = make(map[string]interface{}) CopyMapEntries(source, result) return result, nil } //AsMap converts underlying map as map[string]interface{} func AsMap(source interface{}) map[string]interface{} { if result, err := ToMap(source); err == nil { return result } return nil } //CopyMapEntries appends map entry from source map to target map func CopyMapEntries(sourceMap, targetMap interface{}) { targetMapValue := reflect.ValueOf(targetMap) if targetMapValue.Kind() == reflect.Ptr { targetMapValue = targetMapValue.Elem() } if target, ok := targetMap.(map[string]interface{}); ok { _ = ProcessMap(sourceMap, func(key, value interface{}) bool { target[AsString(key)] = value return true }) return } _ = ProcessMap(sourceMap, func(key, value interface{}) bool { targetMapValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(value)) return true }) } //MapKeysToSlice appends all map keys to targetSlice func MapKeysToSlice(sourceMap interface{}, targetSlicePointer interface{}) error { AssertPointerKind(targetSlicePointer, reflect.Slice, "targetSlicePointer") slicePointerValue := reflect.ValueOf(targetSlicePointer).Elem() return ProcessMap(sourceMap, func(key, value interface{}) bool { slicePointerValue.Set(reflect.Append(slicePointerValue, reflect.ValueOf(key))) return true }) } //MapKeysToStringSlice creates a string slice from sourceMap keys, keys do not need to be of a string type. func MapKeysToStringSlice(sourceMap interface{}) []string { var keys = make([]string, 0) //common cases switch aMap := sourceMap.(type) { case map[string]interface{}: for k := range aMap { keys = append(keys, k) } return keys case map[string]bool: for k := range aMap { keys = append(keys, k) } return keys case map[string]int: for k := range aMap { keys = append(keys, k) } return keys } _ = ProcessMap(sourceMap, func(key interface{}, value interface{}) bool { keys = append(keys, AsString(key)) return true }) return keys } //Process2DSliceInBatches iterates over any 2 dimensional slice, it calls handler with batch. func Process2DSliceInBatches(slice [][]interface{}, size int, handler func(batchedSlice [][]interface{})) { batchCount := (len(slice) / size) + 1 fromIndex, toIndex := 0, 0 for i := 0; i < batchCount; i++ { toIndex = size * (i + 1) isLastBatch := toIndex >= len(slice) if isLastBatch { toIndex = len(slice) } handler(slice[fromIndex:toIndex]) fromIndex = toIndex } } //SortStrings creates a new copy of passed in slice and sorts it. func SortStrings(source []string) []string { var result = make([]string, 0) result = append(result, source...) sort.Strings(result) return result } //JoinAsString joins all items of a slice, with separator, it takes any slice as argument, func JoinAsString(slice interface{}, separator string) string { result := "" ProcessSlice(slice, func(item interface{}) bool { if len(result) > 0 { result = result + separator } result = fmt.Sprintf("%v%v", result, item) return true }) return result } //MakeStringMap creates a mapstring]string from string, func MakeStringMap(text string, valueSeparator string, itemSeparator string) map[string]string { var result = make(map[string]string) for _, item := range strings.Split(text, itemSeparator) { if len(item) == 0 { continue } keyValue := strings.SplitN(item, valueSeparator, 2) if len(keyValue) == 2 { result[strings.Trim(keyValue[0], " \t")] = strings.Trim(keyValue[1], " \n\t") } } return result } //MakeMap creates a mapstring]interface{} from string, func MakeMap(text string, valueSeparator string, itemSeparator string) map[string]interface{} { var result = make(map[string]interface{}) for _, item := range strings.Split(text, itemSeparator) { if len(item) == 0 { continue } keyValue := strings.SplitN(item, valueSeparator, 2) if len(keyValue) == 2 { result[strings.Trim(keyValue[0], " \t")] = strings.Trim(keyValue[1], " \n\t") } } return result } //MakeReverseStringMap creates a mapstring]string from string, the values become key, and key values func MakeReverseStringMap(text string, valueSepartor string, itemSeparator string) map[string]string { var result = make(map[string]string) for _, item := range strings.Split(text, itemSeparator) { if len(item) == 0 { continue } keyValue := strings.SplitN(item, valueSepartor, 2) if len(keyValue) == 2 { result[strings.Trim(keyValue[1], " \t")] = strings.Trim(keyValue[0], " \n\t") } } return result } func isNilOrEmpty(v interface{}) bool { if v == nil { return true } switch value := v.(type) { case string: if value == "" { return true } case int: if value == 0 { return true } case map[string]interface{}: if len(value) == 0 { return true } case map[interface{}]interface{}: if len(value) == 0 { return true } case []map[string]interface{}: if len(value) == 0 { return true } case []map[interface{}]interface{}: if len(value) == 0 { return true } case []interface{}: if len(value) == 0 { return true } case interface{}: if value == nil { return true } } return AsString(v) == "" } //CopyMap copy source map into destination map, copier function can modify key, value, or return false to skip map entry func CopyMap(input, output interface{}, copier func(key, value interface{}) (interface{},interface{}, bool)) (err error) { var mutator func(k, v interface{}) if aMap, ok := output.(map[interface{}]interface{}); ok { mutator = func(k, v interface{}) { aMap[k] = v } } else if aMap, ok := output.(map[string]interface{}); ok { mutator = func(k, v interface{}) { aMap[AsString(k)] = v } } else { return fmt.Errorf("unsupported map type: %v", output) } mapProvider := func(source interface{}) func() interface{} { if _, ok := source.(map[interface{}]interface{}); ok { return func() interface{} { return map[interface{}]interface{}{} } } return func() interface{} { return map[string]interface{}{} } } var ok bool err = ProcessMap(input, func(k, v interface{}) bool { k, v, ok = copier(k, v) if ! ok { return true } if v == nil { // } else if IsMap(v) { transformed := mapProvider(v)() err = CopyMap(v, transformed, copier) if err != nil { return false } if isNilOrEmpty(transformed) { return true } v = transformed } else if IsSlice(v) { aSlice := AsSlice(v) var transformed = []interface{}{} for _, item := range aSlice { if isNilOrEmpty(item) { continue } if IsMap(item) { transformedItem := mapProvider(item)() err = CopyMap(item, transformedItem, copier) if err != nil { return false } if isNilOrEmpty(transformedItem) { return true } transformed = append(transformed, transformedItem) } else { transformed = append(transformed, item) } } if len(transformed) == 0 { return true } v = transformed } mutator(k, v) return true }) return err } //OmitEmptyMapWriter return false for all nil or empty values func OmitEmptyMapWriter(key, value interface{}) (interface{}, interface{}, bool){ if value == nil { return key, value, false } if IsPointer(value) { if reflect.ValueOf(value).IsNil() { return key, value, false } } if IsString(value) { return key, value, AsString(value) != "" } if IsBool(value) { return key, value, AsBoolean(value) } if IsNumber(value) { return key, value, AsFloat(value) != 0.0 } return key, value, true } //CopyNonEmptyMapEntries removes empty keys from map result func CopyNonEmptyMapEntries(input, output interface{}) (err error) { return CopyMap(input, output, func(key, value interface{}) (interface{}, interface{}, bool) { if isNilOrEmpty(value) { return key, value, false } return key, value, true }) } //CopyNonEmptyMapEntries removes empty keys from map result func ReplaceMapEntries(input, output interface{}, replacement map[string]interface{}, removeEmpty bool) (err error) { return CopyMap(input, output, func(key, value interface{}) (interface{}, interface{}, bool) { k := AsString(key) if v, ok := replacement[k]; ok { return key, v, ok } if removeEmpty && isNilOrEmpty(value) { return nil, nil, false } return key, value, true }) } //DeleteEmptyKeys removes empty keys from map result func DeleteEmptyKeys(input interface{}) map[string]interface{} { result := map[string]interface{}{} err := CopyNonEmptyMapEntries(input, result) if err == nil { return result } return AsMap(input) } //DeleteEmptyKeys removes empty keys from map result func ReplaceMapKeys(input interface{}, replacement map[string]interface{}, removeEmpty bool) map[string]interface{} { result := map[string]interface{}{} _ = ReplaceMapEntries(input, result, replacement, removeEmpty) return result } //Pairs returns map for pairs. func Pairs(params ...interface{}) map[string]interface{} { var result = make(map[string]interface{}) for i := 0; i+1 < len(params); i += 2 { var key = AsString(params[i]) result[key] = params[i+1] } return result } // Intersect find elements presents in slice a and b, match is appended to result slice //It accept generic slices, All slices should have items of the same type or interface{} type func Intersect(a, b interface{}, resultPointer interface{}) error { if reflect.ValueOf(resultPointer).Kind() != reflect.Ptr { return fmt.Errorf("resultPointer has to be pointer but had: %T", resultPointer) } var aItems = make(map[interface{}]bool) ProcessSlice(a, func(item interface{}) bool { aItems[item] = true return true }) var appendMatch func(item interface{}) error switch aSlicePrt := resultPointer.(type) { case *[]interface{}: appendMatch = func(item interface{}) error { *aSlicePrt = append(*aSlicePrt, item) return nil } case *[]string: appendMatch = func(item interface{}) error { *aSlicePrt = append(*aSlicePrt, AsString(item)) return nil } case *[]int: appendMatch = func(item interface{}) error { intValue, err := ToInt(item) if err != nil { return err } *aSlicePrt = append(*aSlicePrt, intValue) return nil } default: appendMatch = func(item interface{}) error { sliceValue := reflect.ValueOf(resultPointer).Elem() //TODO add check that type of the slice is assignable from item sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(item))) return nil } } var err error ProcessSlice(b, func(item interface{}) bool { if aItems[item] { if err = appendMatch(item); err != nil { return false } } return true }) return err } toolbox-0.33.2/collections_async.go000066400000000000000000000107431374110251100173270ustar00rootroot00000000000000package toolbox import ( "reflect" "sync" ) //TrueValueProvider is a function that returns true, it takes one parameters which ignores, //This provider can be used to make map from slice like map[some type]bool //ProcessSliceAsync iterates over any slice, it calls handler with each element asynchronously func ProcessSliceAsync(slice interface{}, handler func(item interface{}) bool) { //The common cases with reflection for speed var wg sync.WaitGroup if aSlice, ok := slice.([]interface{}); ok { wg.Add(len(aSlice)) for _, item := range aSlice { go func(item interface{}) { defer wg.Done() handler(item) }(item) } wg.Wait() return } if aSlice, ok := slice.([]map[string]interface{}); ok { wg.Add(len(aSlice)) for _, item := range aSlice { go func(item interface{}) { defer wg.Done() handler(item) }(item) } wg.Wait() return } //The common cases with reflection for speed if aSlice, ok := slice.([]string); ok { wg.Add(len(aSlice)) for _, item := range aSlice { go func(item interface{}) { defer wg.Done() handler(item) }(item) } wg.Wait() return } //The common cases with reflection for speed if aSlice, ok := slice.([]int); ok { wg.Add(len(aSlice)) for _, item := range aSlice { go func(item interface{}) { defer wg.Done() handler(item) }(item) } wg.Wait() return } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) wg.Add(sliceValue.Len()) for i := 0; i < sliceValue.Len(); i++ { go func(item interface{}) { defer wg.Done() handler(item) }(sliceValue.Index(i).Interface()) } wg.Wait() } //IndexSlice reads passed in slice and applies function that takes a slice item as argument to return a key value. //passed in resulting map needs to match key type return by a key function, and accept slice item type as argument. func IndexSliceAsync(slice, resultingMap, keyFunction interface{}) { var lock = sync.RWMutex{} mapValue := DiscoverValueByKind(resultingMap, reflect.Map) ProcessSliceAsync(slice, func(item interface{}) bool { result := CallFunction(keyFunction, item) lock.Lock() //otherwise, fatal error: concurrent map writes defer lock.Unlock() mapValue.SetMapIndex(reflect.ValueOf(result[0]), reflect.ValueOf(item)) return true }) } //SliceToMap reads passed in slice to to apply the key and value function for each item. Result of these calls is placed in the resulting map. func SliceToMapAsync(sourceSlice, targetMap, keyFunction, valueFunction interface{}) { //optimized case var wg sync.WaitGroup var lock = sync.RWMutex{} if stringBoolMap, ok := targetMap.(map[string]bool); ok { if stringSlice, ok := sourceSlice.([]string); ok { if valueFunction, ok := keyFunction.(func(string) bool); ok { if keyFunction, ok := keyFunction.(func(string) string); ok { wg.Add(len(stringSlice)) for _, item := range stringSlice { go func(item string) { defer wg.Done() key := keyFunction(item) value := valueFunction(item) lock.Lock() defer lock.Unlock() stringBoolMap[key] = value }(item) } wg.Wait() return } } } } mapValue := DiscoverValueByKind(targetMap, reflect.Map) ProcessSliceAsync(sourceSlice, func(item interface{}) bool { key := CallFunction(keyFunction, item) value := CallFunction(valueFunction, item) lock.Lock() defer lock.Unlock() mapValue.SetMapIndex(reflect.ValueOf(key[0]), reflect.ValueOf(value[0])) return true }) } func ProcessSliceWithIndexAsync(slice interface{}, handler func(index int, item interface{}) bool) { var wg sync.WaitGroup if aSlice, ok := slice.([]interface{}); ok { wg.Add(len(aSlice)) for i, item := range aSlice { go func(i int, item interface{}) { defer wg.Done() handler(i, item) }(i, item) } wg.Wait() return } if aSlice, ok := slice.([]string); ok { wg.Add(len(aSlice)) for i, item := range aSlice { go func(i int, item interface{}) { defer wg.Done() handler(i, item) }(i, item) } wg.Wait() return } if aSlice, ok := slice.([]int); ok { wg.Add(len(aSlice)) for i, item := range aSlice { go func(i int, item interface{}) { defer wg.Done() handler(i, item) }(i, item) } wg.Wait() return } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) wg.Add(sliceValue.Len()) for i := 0; i < sliceValue.Len(); i++ { go func(i int, item interface{}) { defer wg.Done() handler(i, item) }(i, sliceValue.Index(i).Interface()) } wg.Wait() } toolbox-0.33.2/collections_async_test.go000066400000000000000000000237601374110251100203710ustar00rootroot00000000000000package toolbox import ( "github.com/stretchr/testify/assert" "reflect" "strings" "testing" ) func TestIndexSliceAsync(t *testing.T) { { type Foo struct { id int name string } var fooCollection = []Foo{{1, "A"}, {2, "B"}} var indexedMap = make(map[int]Foo) IndexSliceAsync(fooCollection, indexedMap, func(foo Foo) int { return foo.id }) assert.Equal(t, "A", indexedMap[1].name) } { aSlice := []string{"a", "c"} aMap := make(map[string]int) index := 0 SliceToMapAsync(aSlice, aMap, CopyStringValueProvider, func(s string) int { index++ return index }) assert.Equal(t, 2, len(aMap)) } } type sliceItem struct { Id int } func TestReverseSlice(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", "z", } ReverseSlice(aSlice) assert.Equal(t, []interface{}{"z", "adc", "cyz", "def", "abc"}, aSlice) } ReverseSlice(nil) { aSlice := []*sliceItem{ {1}, {10}, } ReverseSlice(aSlice) assert.Equal(t, []*sliceItem{ {10}, {1}, }, aSlice) } } func TestProcessSliceAsync(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", } count := 0 ProcessSliceAsync(aSlice, func(item interface{}) bool { count++ return true }) assert.Equal(t, 4, count) } { aSlice := []string{ "abc", "def", "cyz", "adc", } count := 0 ProcessSliceAsync(aSlice, func(item interface{}) bool { count++ return true }) assert.Equal(t, 4, count) } } func TestProcessSliceWithIndexAsync(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", } count := 0 ProcessSliceWithIndexAsync(aSlice, func(index int, item interface{}) bool { count += 1 //Test case changed due to index being async return true }) assert.Equal(t, 4, count) } { aSlice := []string{ "abc", "def", "cyz", "adc", } count := 0 ProcessSliceWithIndexAsync(aSlice, func(index int, item interface{}) bool { count += 1 return true }) assert.Equal(t, 4, count) } } func TestMakeMapFromSliceAsync(t *testing.T) { type Foo struct { id int name string } var fooCollection = []Foo{{1, "A"}, {2, "B"}} var testMap = make(map[int]string) SliceToMapAsync(fooCollection, testMap, func(foo Foo) int { return foo.id }, func(foo Foo) string { return foo.name }) assert.Equal(t, "A", testMap[1]) assert.Equal(t, "B", testMap[2]) } func TestSliceToMapAsync(t *testing.T) { aSlice := []string{"a", "c"} aMap := make(map[string]bool) SliceToMapAsync(aSlice, aMap, func(s string) string { return s }, func(s string) bool { return true }) assert.Equal(t, 2, len(aMap)) } func TestProcess2DSliceInBatches(t *testing.T) { slice := [][]interface{}{ {1, 2, 3}, {4, 5, 7}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15}, {16, 17, 18}, {19, 20, 21}, } actualItemCount := 0 Process2DSliceInBatches(slice, 2, func(item [][]interface{}) { actualItemCount = actualItemCount + len(item) }) assert.Equal(t, 7, actualItemCount) } func TestCopySliceElements(t *testing.T) { { source := []interface{}{ "abc", "def", "cyz", } var target = make([]string, 0) CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } { source := []interface{}{ 1, 2, 3, } var target = make([]int, 0) CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } { source := []interface{}{ 1, 2, 3, } var target = make([]interface{}, 0) CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } } func TestFilterSliceElements(t *testing.T) { { source := []interface{}{ 1, 2, 3, } var target = make([]int, 0) //filter all elements starting with a FilterSliceElements(source, func(item int) bool { return item > 1 }, &target) assert.Equal(t, 2, len(target)) assert.Equal(t, 2, target[0]) assert.Equal(t, 3, target[1]) } { source := []interface{}{ "abc", "def", "cyz", "adc", } var target = make([]string, 0) //filter all elements starting with a FilterSliceElements(source, func(item string) bool { return strings.HasPrefix(item, "a") }, &target) assert.Equal(t, 2, len(target)) assert.Equal(t, "abc", target[0]) assert.Equal(t, "adc", target[1]) } } func TestHasSliceAnyElements(t *testing.T) { source := []interface{}{ "abc", "def", "cyz", "adc", } assert.True(t, HasSliceAnyElements(source, "cyz")) assert.False(t, HasSliceAnyElements(source, "cyze")) assert.True(t, HasSliceAnyElements(source, "cyze", "cyz")) } func TestMapKeysToSlice(t *testing.T) { m := map[string]int{ "abc": 1, "efg": 2, } var keys = make([]string, 0) MapKeysToSlice(m, &keys) assert.Equal(t, 2, len(keys)) } func TestMapKeysToStringSlice(t *testing.T) { m := map[string]int{ "abc": 1, "efg": 2, } slice := MapKeysToStringSlice(m) assert.Equal(t, 2, len(slice)) } func TestCopyMapEntries(t *testing.T) { type Foo struct { id int name string } source := map[interface{}]interface{}{ 1: Foo{1, "A"}, 2: Foo{2, "B"}, } var target = make(map[int]Foo) CopyMapEntries(source, target) assert.Equal(t, 2, len(target)) assert.Equal(t, "B", target[2].name) } func TestIndexMultimap(t *testing.T) { type Product struct{ vendor, name string } products := []Product{ {"Vendor1", "Product1"}, {"Vendor2", "Product2"}, {"Vendor1", "Product3"}, {"Vendor1", "Product4"}, } productsByVendor := make(map[string][]Product) GroupSliceElements(products, productsByVendor, func(product Product) string { return product.vendor }) assert.Equal(t, 2, len(productsByVendor)) assert.Equal(t, 3, len(productsByVendor["Vendor1"])) assert.Equal(t, "Product4", productsByVendor["Vendor1"][2].name) } func TestSliceToMultiMap(t *testing.T) { type Product struct { vendor, name string productId int } products := []Product{ {"Vendor1", "Product1", 1}, {"Vendor2", "Product2", 2}, {"Vendor1", "Product3", 3}, {"Vendor1", "Product4", 4}, } productsByVendor := make(map[string][]int) SliceToMultimap(products, productsByVendor, func(product Product) string { return product.vendor }, func(product Product) int { return product.productId }) assert.Equal(t, 2, len(productsByVendor)) assert.Equal(t, 3, len(productsByVendor["Vendor1"])) assert.Equal(t, 4, productsByVendor["Vendor1"][2]) } func TestTransformSlice(t *testing.T) { type Product struct{ vendor, name string } products := []Product{ {"Vendor1", "Product1"}, {"Vendor2", "Product2"}, {"Vendor1", "Product3"}, {"Vendor1", "Product4"}, } var vendors = make([]string, 0) TransformSlice(products, &vendors, func(product Product) string { return product.vendor }) assert.Equal(t, 4, len(vendors)) assert.Equal(t, "Vendor1", vendors[3]) } func TestMakeStringMap(t *testing.T) { aMap := MakeStringMap("a:1, b:2", ":", ",") assert.Equal(t, 2, len(aMap)) assert.Equal(t, "1", aMap["a"]) assert.Equal(t, "2", aMap["b"]) } func TestMakeReverseStringMap(t *testing.T) { aMap := MakeReverseStringMap("a:1, b:2", ":", ",") assert.Equal(t, 2, len(aMap)) assert.Equal(t, "a", aMap["1"]) assert.Equal(t, "b", aMap["2"]) } func TestSortStrings(t *testing.T) { sorted := SortStrings([]string{"z", "b", "c", "a"}) assert.Equal(t, "a", sorted[0]) assert.Equal(t, "z", sorted[3]) } func TestJoinAsString(t *testing.T) { assert.Equal(t, "a,b", JoinAsString([]string{"a", "b"}, ",")) } func TestSetSliceValue(t *testing.T) { { var aSlice = make([]string, 2) SetSliceValue(aSlice, 0, "abc") assert.Equal(t, "abc", aSlice[0]) assert.Equal(t, "abc", GetSliceValue(aSlice, 0)) } { var aSlice = make([]int, 2) SetSliceValue(aSlice, 0, 100) assert.Equal(t, 100, aSlice[0]) assert.Equal(t, 100, GetSliceValue(aSlice, 0)) } { var aSlice = make([]interface{}, 2) SetSliceValue(aSlice, 0, "a") assert.Equal(t, "a", aSlice[0]) assert.Equal(t, "a", GetSliceValue(aSlice, 0)) } } func TestTrueValueProvider(t *testing.T) { assert.True(t, TrueValueProvider(1)) } func Test_DeleteEmptyKeys(t *testing.T) { aMap := map[string]interface{}{ "k1": []int{}, "k2": []int{1}, "k3": "", "k40": map[interface{}]interface{}{ "k1": nil, 1: 2, "k31": []map[string]interface{}{}, "k41": []map[string]interface{}{ { "z": 1, }, }, }, "k5": map[string]interface{}{ "k1": "", "10": 20, }, } cloned := DeleteEmptyKeys(aMap) assert.Equal(t, map[string]interface{}{ "k2": []interface{}{1}, "k40": map[interface{}]interface{}{ 1: 2, "k41": []interface{}{ map[string]interface{}{ "z": 1, }, }, }, "k5": map[string]interface{}{ "10": 20, }, }, cloned) } func TestIntersection(t *testing.T) { useCase1Actual := []string{} useCase2Actual := []int{} useCase3Actual := []float32{} var useCases = []struct { description string sliceA interface{} sliceB interface{} actual interface{} expect interface{} hasError bool }{ { description: "string slice intersection", sliceA: []string{"a", "bc", "z", "eee"}, sliceB: []string{"a2", "bc", "5z", "eee"}, actual: &useCase1Actual, expect: []string{"bc", "eee"}, }, { description: "int slice intersection", sliceA: []int{1, 2, 3, 4}, sliceB: []int{3, 4, 5, 6}, actual: &useCase2Actual, expect: []int{3, 4}, }, { description: "float slice intersection", sliceA: []float32{1.1, 2.1, 3.1, 4.1}, sliceB: []float32{3.1, 4.1, 5.1, 6.1}, actual: &useCase3Actual, expect: []float32{3.1, 4.1}, }, } for _, useCase := range useCases { err := Intersect(useCase.sliceA, useCase.sliceB, useCase.actual) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } if !assert.Nil(t, err, useCase.description) { continue } actual := reflect.ValueOf(useCase.actual).Elem().Interface() assert.EqualValues(t, useCase.expect, actual, useCase.description) } } toolbox-0.33.2/collections_test.go000066400000000000000000000271031374110251100171670ustar00rootroot00000000000000package toolbox_test import ( "strings" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "reflect" ) func TestIndexSlice(t *testing.T) { { type Foo struct { id int name string } var fooCollection = []Foo{{1, "A"}, {2, "B"}} var indexedMap = make(map[int]Foo) toolbox.IndexSlice(fooCollection, indexedMap, func(foo Foo) int { return foo.id }) assert.Equal(t, "A", indexedMap[1].name) } { aSlice := []string{"a", "c"} aMap := make(map[string]int) index := 0 toolbox.SliceToMap(aSlice, aMap, toolbox.CopyStringValueProvider, func(s string) int { index++ return index }) assert.Equal(t, 2, len(aMap)) } } type sliceItem struct { Id int } func TestReverseSlice(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", "z", } toolbox.ReverseSlice(aSlice) assert.Equal(t, []interface{}{"z", "adc", "cyz", "def", "abc"}, aSlice) } toolbox.ReverseSlice(nil) { aSlice := []*sliceItem{ {1}, {10}, } toolbox.ReverseSlice(aSlice) assert.Equal(t, []*sliceItem{ {10}, {1}, }, aSlice) } } func TestProcessSlice(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSlice(aSlice, func(item interface{}) bool { count++ return true }) assert.Equal(t, 4, count) } { aSlice := []string{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSlice(aSlice, func(item interface{}) bool { count++ return false }) assert.Equal(t, 1, count) } { aSlice := []int{ 1, 2, 3, } count := 0 toolbox.ProcessSlice(aSlice, func(item interface{}) bool { count++ return false }) assert.Equal(t, 1, count) } { aSlice := []string{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSlice(aSlice, func(item interface{}) bool { count++ return true }) assert.Equal(t, 4, count) } { aSlice := []interface{}{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSlice(aSlice, func(item interface{}) bool { count++ return false }) assert.Equal(t, 1, count) } } func TestProcessSliceWithIndex(t *testing.T) { { aSlice := []interface{}{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSliceWithIndex(aSlice, func(index int, item interface{}) bool { count = index return true }) assert.Equal(t, 3, count) } { aSlice := []string{ "abc", "def", "cyz", "adc", } count := 0 toolbox.ProcessSliceWithIndex(aSlice, func(index int, item interface{}) bool { count = index return true }) assert.Equal(t, 3, count) } } func TestMakeMapFromSlice(t *testing.T) { type Foo struct { id int name string } var fooCollection = []Foo{{1, "A"}, {2, "B"}} var testMap = make(map[int]string) toolbox.SliceToMap(fooCollection, testMap, func(foo Foo) int { return foo.id }, func(foo Foo) string { return foo.name }) assert.Equal(t, "A", testMap[1]) assert.Equal(t, "B", testMap[2]) } func TestSliceToMap(t *testing.T) { aSlice := []string{"a", "c"} aMap := make(map[string]bool) toolbox.SliceToMap(aSlice, aMap, func(s string) string { return s }, func(s string) bool { return true }) assert.Equal(t, 2, len(aMap)) } func TestProcess2DSliceInBatches(t *testing.T) { slice := [][]interface{}{ {1, 2, 3}, {4, 5, 7}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15}, {16, 17, 18}, {19, 20, 21}, } actualItemCount := 0 toolbox.Process2DSliceInBatches(slice, 2, func(item [][]interface{}) { actualItemCount = actualItemCount + len(item) }) assert.Equal(t, 7, actualItemCount) } func TestCopySliceElements(t *testing.T) { { source := []interface{}{ "abc", "def", "cyz", } var target = make([]string, 0) toolbox.CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } { source := []interface{}{ 1, 2, 3, } var target = make([]int, 0) toolbox.CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } { source := []interface{}{ 1, 2, 3, } var target = make([]interface{}, 0) toolbox.CopySliceElements(source, &target) assert.Equal(t, 3, len(target)) for i := 0; i < len(source); i++ { assert.Equal(t, source[i], target[i]) } } } func TestFilterSliceElements(t *testing.T) { { source := []interface{}{ 1, 2, 3, } var target = make([]int, 0) //filter all elements starting with a toolbox.FilterSliceElements(source, func(item int) bool { return item > 1 }, &target) assert.Equal(t, 2, len(target)) assert.Equal(t, 2, target[0]) assert.Equal(t, 3, target[1]) } { source := []interface{}{ "abc", "def", "cyz", "adc", } var target = make([]string, 0) //filter all elements starting with a toolbox.FilterSliceElements(source, func(item string) bool { return strings.HasPrefix(item, "a") }, &target) assert.Equal(t, 2, len(target)) assert.Equal(t, "abc", target[0]) assert.Equal(t, "adc", target[1]) } } func TestHasSliceAnyElements(t *testing.T) { source := []interface{}{ "abc", "def", "cyz", "adc", } assert.True(t, toolbox.HasSliceAnyElements(source, "cyz")) assert.False(t, toolbox.HasSliceAnyElements(source, "cyze")) assert.True(t, toolbox.HasSliceAnyElements(source, "cyze", "cyz")) } func TestMapKeysToSlice(t *testing.T) { m := map[string]int{ "abc": 1, "efg": 2, } var keys = make([]string, 0) toolbox.MapKeysToSlice(m, &keys) assert.Equal(t, 2, len(keys)) } func TestMapKeysToStringSlice(t *testing.T) { m := map[string]int{ "abc": 1, "efg": 2, } slice := toolbox.MapKeysToStringSlice(m) assert.Equal(t, 2, len(slice)) } func TestCopyMapEntries(t *testing.T) { type Foo struct { id int name string } source := map[interface{}]interface{}{ 1: Foo{1, "A"}, 2: Foo{2, "B"}, } var target = make(map[int]Foo) toolbox.CopyMapEntries(source, target) assert.Equal(t, 2, len(target)) assert.Equal(t, "B", target[2].name) } func TestIndexMultimap(t *testing.T) { type Product struct{ vendor, name string } products := []Product{ {"Vendor1", "Product1"}, {"Vendor2", "Product2"}, {"Vendor1", "Product3"}, {"Vendor1", "Product4"}, } productsByVendor := make(map[string][]Product) toolbox.GroupSliceElements(products, productsByVendor, func(product Product) string { return product.vendor }) assert.Equal(t, 2, len(productsByVendor)) assert.Equal(t, 3, len(productsByVendor["Vendor1"])) assert.Equal(t, "Product4", productsByVendor["Vendor1"][2].name) } func TestSliceToMultiMap(t *testing.T) { type Product struct { vendor, name string productId int } products := []Product{ {"Vendor1", "Product1", 1}, {"Vendor2", "Product2", 2}, {"Vendor1", "Product3", 3}, {"Vendor1", "Product4", 4}, } productsByVendor := make(map[string][]int) toolbox.SliceToMultimap(products, productsByVendor, func(product Product) string { return product.vendor }, func(product Product) int { return product.productId }) assert.Equal(t, 2, len(productsByVendor)) assert.Equal(t, 3, len(productsByVendor["Vendor1"])) assert.Equal(t, 4, productsByVendor["Vendor1"][2]) } func TestTransformSlice(t *testing.T) { type Product struct{ vendor, name string } products := []Product{ {"Vendor1", "Product1"}, {"Vendor2", "Product2"}, {"Vendor1", "Product3"}, {"Vendor1", "Product4"}, } var vendors = make([]string, 0) toolbox.TransformSlice(products, &vendors, func(product Product) string { return product.vendor }) assert.Equal(t, 4, len(vendors)) assert.Equal(t, "Vendor1", vendors[3]) } func TestMakeStringMap(t *testing.T) { aMap := toolbox.MakeStringMap("a:1, b:2", ":", ",") assert.Equal(t, 2, len(aMap)) assert.Equal(t, "1", aMap["a"]) assert.Equal(t, "2", aMap["b"]) } func TestMakeReverseStringMap(t *testing.T) { aMap := toolbox.MakeReverseStringMap("a:1, b:2", ":", ",") assert.Equal(t, 2, len(aMap)) assert.Equal(t, "a", aMap["1"]) assert.Equal(t, "b", aMap["2"]) } func TestSortStrings(t *testing.T) { sorted := toolbox.SortStrings([]string{"z", "b", "c", "a"}) assert.Equal(t, "a", sorted[0]) assert.Equal(t, "z", sorted[3]) } func TestJoinAsString(t *testing.T) { assert.Equal(t, "a,b", toolbox.JoinAsString([]string{"a", "b"}, ",")) } func TestSetSliceValue(t *testing.T) { { var aSlice = make([]string, 2) toolbox.SetSliceValue(aSlice, 0, "abc") assert.Equal(t, "abc", aSlice[0]) assert.Equal(t, "abc", toolbox.GetSliceValue(aSlice, 0)) } { var aSlice = make([]int, 2) toolbox.SetSliceValue(aSlice, 0, 100) assert.Equal(t, 100, aSlice[0]) assert.Equal(t, 100, toolbox.GetSliceValue(aSlice, 0)) } { var aSlice = make([]interface{}, 2) toolbox.SetSliceValue(aSlice, 0, "a") assert.Equal(t, "a", aSlice[0]) assert.Equal(t, "a", toolbox.GetSliceValue(aSlice, 0)) } } func TestTrueValueProvider(t *testing.T) { assert.True(t, toolbox.TrueValueProvider(1)) } func Test_DeleteEmptyKeys(t *testing.T) { aMap := map[string]interface{}{ "k1": []int{}, "k2": []int{1}, "k3": "", "k40": map[interface{}]interface{}{ "k1": nil, 1: 2, "k31": []map[string]interface{}{}, "k41": []map[string]interface{}{ { "z": 1, }, }, }, "k5": map[string]interface{}{ "k1": "", "10": 20, }, } cloned := toolbox.DeleteEmptyKeys(aMap) assert.Equal(t, map[string]interface{}{ "k2": []interface{}{1}, "k40": map[interface{}]interface{}{ 1: 2, "k41": []interface{}{ map[string]interface{}{ "z": 1, }, }, }, "k5": map[string]interface{}{ "10": 20, }, }, cloned) } func TestReplaceMapKeys(t *testing.T) { aMap := map[string]interface{}{ "k1": []int{}, "k2": []int{1}, "k3": "", "k40": map[interface{}]interface{}{ "k1": nil, 1: 2, "k31": []map[string]interface{}{}, "k41": []map[string]interface{}{ { "z": 1, }, }, }, "k5": map[string]interface{}{ "k1": "", "k10": 20, }, } replaced := toolbox.ReplaceMapKeys(aMap, map[string]interface{}{ "k1": 123, "k10": 30, }, true) assert.EqualValues(t, replaced, map[string]interface{}{ "k1": 123, "k2": []interface{}{1}, "k40": map[interface{}]interface{}{ "k1": 123, 1: 2, "k41": []interface{}{ map[string]interface{}{ "z": 1, }, }, }, "k5": map[string]interface{}{ "k10": 30, "k1": 123, }, }) } func TestIntersection(t *testing.T) { useCase1Actual := []string{} useCase2Actual := []int{} useCase3Actual := []float32{} var useCases = []struct { description string sliceA interface{} sliceB interface{} actual interface{} expect interface{} hasError bool }{ { description: "string slice intersection", sliceA: []string{"a", "bc", "z", "eee"}, sliceB: []string{"a2", "bc", "5z", "eee"}, actual: &useCase1Actual, expect: []string{"bc", "eee"}, }, { description: "int slice intersection", sliceA: []int{1, 2, 3, 4}, sliceB: []int{3, 4, 5, 6}, actual: &useCase2Actual, expect: []int{3, 4}, }, { description: "float slice intersection", sliceA: []float32{1.1, 2.1, 3.1, 4.1}, sliceB: []float32{3.1, 4.1, 5.1, 6.1}, actual: &useCase3Actual, expect: []float32{3.1, 4.1}, }, } for _, useCase := range useCases { err := toolbox.Intersect(useCase.sliceA, useCase.sliceB, useCase.actual) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } if !assert.Nil(t, err, useCase.description) { continue } actual := reflect.ValueOf(useCase.actual).Elem().Interface() assert.EqualValues(t, useCase.expect, actual, useCase.description) } } toolbox-0.33.2/context.go000066400000000000000000000074351374110251100153040ustar00rootroot00000000000000package toolbox import ( "fmt" "reflect" ) //Context represents type safe map. type Context interface { //GetRequired returns a value for a target type of error if it does not exist GetRequired(targetType interface{}) (interface{}, error) //GetOptional returns a value for a target type GetOptional(targetType interface{}) interface{} //GetOptional into sets requested context value into target, returns true if value was found GetInto(targetType interface{}, target interface{}) bool //Put puts target type value to the context, or error if value exists, is nil or incompatible with target type Put(targetType interface{}, value interface{}) error //Replace repaces value in the context Replace(targetType interface{}, value interface{}) error //Remove removes value from the context Remove(targetType interface{}) interface{} //Contains chekcs if a value of a terget type is in contet Contains(targetType interface{}) bool //Clone create a shallow copy of a context Clone() Context } type contextImpl struct { context map[string]interface{} } func (c *contextImpl) getReflectType(targetType interface{}) reflect.Type { var reflectType reflect.Type var ok bool reflectType, ok = targetType.(reflect.Type) if !ok { reflectType = reflect.TypeOf(targetType) } return reflectType } func (c *contextImpl) getKey(targetType interface{}) string { var reflectType = c.getReflectType(targetType) return reflectType.String() } func (c *contextImpl) GetRequired(targetType interface{}) (interface{}, error) { if !c.Contains(targetType) { key := c.getKey(targetType) return nil, fmt.Errorf("failed to lookup key:" + key) } return c.GetOptional(targetType), nil } func (c *contextImpl) GetOptional(targetType interface{}) interface{} { key := c.getKey(targetType) if result, ok := c.context[key]; ok { return result } return nil } func (c *contextImpl) GetInto(targetType, target interface{}) bool { key := c.getKey(targetType) if result, ok := c.context[key]; ok { reflect.ValueOf(target).Elem().Set(reflect.ValueOf(result)) return true } return false } func (c *contextImpl) Put(targetType interface{}, value interface{}) error { if c.Contains(targetType) { key := c.getKey(targetType) return fmt.Errorf("failed to put key - already exist: " + key) } return c.Replace(targetType, value) } func (c *contextImpl) Replace(targetType interface{}, value interface{}) error { key := c.getKey(targetType) targetReflectType := c.getReflectType(targetType) valueReflectType := reflect.TypeOf(value) if valueReflectType == targetReflectType { c.context[key] = value return nil } if targetReflectType.Kind() == reflect.Ptr { converted := reflect.ValueOf(value).Elem().Convert(targetReflectType.Elem()) convertedPointer := reflect.New(targetReflectType.Elem()) convertedPointer.Elem().Set(converted) value = convertedPointer.Interface() } else { if !valueReflectType.AssignableTo(targetReflectType) { return fmt.Errorf("value of type %v is not assignable to %v", valueReflectType, targetReflectType) } value = reflect.ValueOf(value).Convert(targetReflectType).Interface() } c.context[key] = value return nil } func (c *contextImpl) Remove(targetType interface{}) interface{} { key := c.getKey(targetType) result := c.GetOptional(targetType) delete(c.context, key) return result } func (c *contextImpl) Contains(targetType interface{}) bool { key := c.getKey(targetType) if _, ok := c.context[key]; ok { return true } return false } func (c *contextImpl) Clone() Context { var result = &contextImpl{context: make(map[string]interface{})} for k, v := range c.context { result.context[k] = v } return result } //NewContext creates a new context func NewContext() Context { var result Context = &contextImpl{context: make(map[string]interface{})} return result } toolbox-0.33.2/context_test.go000066400000000000000000000042271374110251100163370ustar00rootroot00000000000000package toolbox_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) type IMessage interface { Message() string } type Message struct { message string } func (this Message) Message() string { return this.message } func TestContext(t *testing.T) { context := toolbox.NewContext() message1 := Message{message: "abc"} //operate on pointer test assert.False(t, context.Contains((*Message)(nil)), "Should not have message in context") err := context.Put((*Message)(nil), &message1) assert.Nil(t, err) assert.True(t, context.Contains((*Message)(nil)), "Should have meesage in context") assert.True(t, context.Contains(&Message{}), "Should have meesage in context") value, err := context.GetRequired((*Message)(nil)) assert.Nil(t, err) m1 := value.(*Message) assert.Equal(t, "abc", m1.message, "should have the same value field") m10 := &Message{} context.GetInto((*Message)(nil), &m10) assert.Equal(t, "abc", m10.message, "should have the same value field") m1.message = "xyz" assert.Equal(t, "xyz", message1.message, "should have the same value field") assert.Equal(t, "xyz", m10.message, "should have the same value field") err = context.Put((*IMessage)(nil), &message1) assert.Nil(t, err) m2 := context.GetOptional((*IMessage)(nil)).(*IMessage) assert.Equal(t, "xyz", (*m2).Message(), "should have the same value field") //operate on struct passing by copy does not enable global changes assert.False(t, context.Contains(Message{}), "Should not have message in context") err = context.Put(Message{}, message1) assert.Nil(t, err) m3 := context.GetOptional(Message{}).(Message) assert.Equal(t, "xyz", m3.message, "should have the same value field") m3.message = "123" assert.Equal(t, "123", m3.message, "should have the same value field") assert.Equal(t, "xyz", m1.message, "should have the same value field") err = context.Put(Message{}, message1) assert.NotNil(t, err, "Key is already in context") err = context.Replace(1, "abc") assert.NotNil(t, err, "Incompatible type") removed := context.Remove((*IMessage)(nil)) assert.NotNil(t, removed) _, err = context.GetRequired("abc") assert.NotNil(t, err) } toolbox-0.33.2/converter.go000066400000000000000000001165101374110251100156220ustar00rootroot00000000000000package toolbox import ( "errors" "fmt" "math" "reflect" "strconv" "strings" "time" ) //DefaultDateLayout is set to 2006-01-02 15:04:05.000 var DefaultDateLayout = "2006-01-02 15:04:05.000" var numericTypes = []reflect.Type{ reflect.TypeOf(int(0)), reflect.TypeOf(int8(0)), reflect.TypeOf(int16(0)), reflect.TypeOf(int32(0)), reflect.TypeOf(int64(0)), reflect.TypeOf(uint(0)), reflect.TypeOf(uint8(0)), reflect.TypeOf(uint16(0)), reflect.TypeOf(uint32(0)), reflect.TypeOf(uint64(0)), reflect.TypeOf(float32(0.0)), reflect.TypeOf(float64(0.0)), } //AsString converts an input to string. func AsString(input interface{}) string { switch value := input.(type) { case string: return value case []byte: return string(value) case []interface{}: if len(value) == 0 { return "" } if _, isByte := value[0].(byte); isByte { var stringBytes = make([]byte, len(value)) for i, v := range value { stringBytes[i] = v.(byte) } return string(stringBytes) } var result = "" for _, v := range value { result += AsString(v) } return result } reflectValue := reflect.ValueOf(input) if reflectValue.Kind() == reflect.Ptr { reflectValue = reflectValue.Elem() } switch reflectValue.Kind() { case reflect.Bool: return strconv.FormatBool(reflectValue.Bool()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(reflectValue.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return strconv.FormatUint(reflectValue.Uint(), 10) case reflect.Float64: return strconv.FormatFloat(reflectValue.Float(), 'g', -1, 64) case reflect.Float32: return strconv.FormatFloat(reflectValue.Float(), 'g', -1, 32) } return fmt.Sprintf("%v", input) } //CanConvertToFloat checkis if float conversion is possible. func CanConvertToFloat(value interface{}) bool { if _, ok := value.(float64); ok { return true } _, err := strconv.ParseFloat(AsString(value), 64) return err == nil } //AsFloat converts an input to float. func AsFloat(value interface{}) float64 { if result, err := ToFloat(value); err == nil { return result } return 0 } //ToFloat converts an input to float or error func ToFloat(value interface{}) (float64, error) { if value == nil { return 0, NewNilPointerError("float value was nil") } switch actualValue := value.(type) { case float64: return actualValue, nil case int: return float64(actualValue), nil case uint: return float64(actualValue), nil case int64: return float64(actualValue), nil case uint64: return float64(actualValue), nil case int32: return float64(actualValue), nil case uint32: return float64(actualValue), nil case float32: return float64(actualValue), nil case bool: if actualValue { return 1.0, nil } return 0.0, nil } if reflect.TypeOf(value).Kind() == reflect.Ptr { floatValue, err := ToFloat(DereferenceValue(value)) if err != nil && IsNilPointerError(err) { return floatValue, nil } return floatValue, err } valueAsString := AsString(DereferenceValue(value)) return strconv.ParseFloat(valueAsString, 64) } //ToBoolean converts an input to bool. func ToBoolean(value interface{}) (bool, error) { if boolValue, ok := value.(bool); ok { return boolValue, nil } valueAsString := AsString(value) return strconv.ParseBool(valueAsString) } //AsBoolean converts an input to bool. func AsBoolean(value interface{}) bool { result, err := ToBoolean(value) if err != nil { return false } return result } //CanConvertToInt returns true if an input can be converted to int value. func CanConvertToInt(value interface{}) bool { if _, ok := value.(int); ok { return true } valueAsString := AsString(value) if _, err := strconv.ParseInt(valueAsString, 10, 64); err == nil { return true } return false } var intBitSize = reflect.TypeOf(int64(0)).Bits() //AsInt converts an input to int. func AsInt(value interface{}) int { var result, err = ToInt(value) if err == nil { return result } return 0 } //ToInt converts input value to int or error func ToInt(value interface{}) (int, error) { if value == nil { return 0, NewNilPointerError("int value was nil") } switch actual := value.(type) { case int: return actual, nil case int8: return int(actual), nil case int16: return int(actual), nil case int32: return int(actual), nil case int64: return int(actual), nil case uint: return int(actual), nil case uint8: return int(actual), nil case uint16: return int(actual), nil case uint32: return int(actual), nil case uint64: return int(actual), nil case float32: return int(actual), nil case float64: return int(actual), nil case bool: if actual { return 1, nil } return 0, nil } if reflect.TypeOf(value).Kind() == reflect.Ptr { value := DereferenceValue(value) if intValue, err := ToInt(value); err != nil { if err != nil && IsNilPointerError(err) { return intValue, err } return intValue, err } } valueAsString := AsString(value) if strings.Contains(valueAsString, ".") { floatValue, err := strconv.ParseFloat(valueAsString, intBitSize) if err != nil { return 0, err } return int(floatValue), nil } result, err := strconv.ParseInt(valueAsString, 10, 64) return int(result), err } func unitToTime(timestamp int64) *time.Time { var timeValue time.Time if timestamp > math.MaxUint32 { var timestampInMs = timestamp / 1000000 if timestampInMs > math.MaxUint32 { timeValue = time.Unix(0, timestamp) } else { timeValue = time.Unix(0, timestamp*1000000) } } else { timeValue = time.Unix(timestamp, 0) } return &timeValue } func textToTime(value, dateLayout string) (*time.Time, error) { floatValue, err := ToFloat(value) if err == nil { return unitToTime(int64(floatValue)), nil } timeValue, err := ParseTime(value, dateLayout) if err != nil { if dateLayout != "" { if len(value) > len(dateLayout) { value = string(value[:len(dateLayout)]) } timeValue, err = ParseTime(value, dateLayout) } if err != nil { //JSON default time format fallback if timeValue, err = ParseTime(value, time.RFC3339); err == nil { return &timeValue, err } return nil, err } } return &timeValue, nil } //ToTime converts value to time, optionally uses layout if value if of string type func ToTime(value interface{}, dateLayout string) (*time.Time, error) { if value == nil { return nil, errors.New("values was empty") } switch actual := value.(type) { case time.Time: return &actual, nil case *time.Time: return actual, nil case float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: var floatValue, _ = ToFloat(value) return unitToTime(int64(floatValue)), nil case *float32, *float64, *int, *int8, *int16, *int32, *int64, *uint, *uint8, *uint16, *uint32, *uint64: actual = DereferenceValue(actual) return ToTime(actual, dateLayout) case string: return textToTime(actual, dateLayout) default: textValue := AsString(DereferenceValue(actual)) return textToTime(textValue, dateLayout) } } //AsTime converts an input to time, it takes time input, dateLaout as parameters. func AsTime(value interface{}, dateLayout string) *time.Time { result, err := ToTime(value, dateLayout) if err != nil { return nil } return result } //DiscoverValueAndKind discovers input kind, it applies checks of the following types: int, float, bool, string func DiscoverValueAndKind(input string) (interface{}, reflect.Kind) { if len(input) == 0 { return nil, reflect.Invalid } if strings.Contains(input, ".") { if floatValue, err := strconv.ParseFloat(input, 64); err == nil { return floatValue, reflect.Float64 } } if intValue, err := strconv.ParseInt(input, 10, 64); err == nil { return int(intValue), reflect.Int } else if strings.ToLower(input) == "true" { return true, reflect.Bool } else if strings.ToLower(input) == "false" { return false, reflect.Bool } return input, reflect.String } //DiscoverCollectionValuesAndKind discovers passed in slice item kind, and returns slice of values converted to discovered type. //It tries the following kind int, float, bool, string func DiscoverCollectionValuesAndKind(values interface{}) ([]interface{}, reflect.Kind) { var candidateKind = reflect.Int var result = make([]interface{}, 0) ProcessSlice(values, func(value interface{}) bool { stringValue := strings.ToLower(AsString(value)) switch candidateKind { case reflect.String: return false case reflect.Int: if !strings.Contains(stringValue, ".") && CanConvertToInt(value) { return true } candidateKind = reflect.Float64 fallthrough case reflect.Float64: if CanConvertToFloat(value) { return true } candidateKind = reflect.Bool fallthrough case reflect.Bool: if stringValue == "true" || stringValue == "false" { return true } candidateKind = reflect.String } return true }) ProcessSlice(values, func(value interface{}) bool { switch candidateKind { case reflect.Float64: result = append(result, AsFloat(value)) case reflect.Int: result = append(result, AsInt(value)) case reflect.Bool: result = append(result, AsBoolean(value)) default: result = append(result, AsString(value)) } return true }) return result, candidateKind } //UnwrapValue returns value func UnwrapValue(value *reflect.Value) interface{} { return value.Interface() } //NewBytes copies from input func NewBytes(input []byte) []byte { if input != nil { var result = make([]byte, len(input)) copy(result, input) return result } return nil } //ParseTime parses time, adjusting date layout to length of input func ParseTime(input, layout string) (time.Time, error) { if len(layout) == 0 { layout = DefaultDateLayout } //GetFieldValue returns field value lastPosition := len(input) if lastPosition >= len(layout) { lastPosition = len(layout) } layout = layout[0:lastPosition] return time.Parse(layout, input) } //Converter represets data converter, it converts incompatibe data structure, like map and struct, string and time, *string to string, etc. type Converter struct { DateLayout string MappedKeyTag string } func (c *Converter) assignConvertedMap(target, source interface{}, targetIndirectValue reflect.Value, targetIndirectPointerType reflect.Type) error { mapType := DiscoverTypeByKind(target, reflect.Map) mapPointer := reflect.New(mapType) mapValueType := mapType.Elem() mapKeyType := mapType.Key() newMap := mapPointer.Elem() newMap.Set(reflect.MakeMap(mapType)) var err error err = ProcessMap(source, func(key, value interface{}) bool { if value == nil { return true } mapValueType = reflect.TypeOf(value) targetMapValuePointer := reflect.New(mapValueType) err = c.AssignConverted(targetMapValuePointer.Interface(), value) if err != nil { err = fmt.Errorf("failed to assigned converted map value %v to %v due to %v", source, target, err) return false } targetMapKeyPointer := reflect.New(mapKeyType) err = c.AssignConverted(targetMapKeyPointer.Interface(), key) if err != nil { err = fmt.Errorf("failed to assigned converted map key %v to %v due to %v", source, target, err) return false } var elementKey = targetMapKeyPointer.Elem() var elementValue = targetMapValuePointer.Elem() if elementKey.Type() != mapKeyType { if elementKey.Type().AssignableTo(mapKeyType) { elementKey = elementKey.Convert(mapKeyType) } } if !elementValue.Type().AssignableTo(newMap.Type().Elem()) { var compatibleValue = reflect.New(newMap.Type().Elem()) err = c.AssignConverted(compatibleValue.Interface(), elementValue.Interface()) if err != nil { return false } elementValue = compatibleValue.Elem() } newMap.SetMapIndex(elementKey, elementValue) return true }) if err != nil { return err } if targetIndirectPointerType.Kind() == reflect.Map { if targetIndirectValue.Type().AssignableTo(mapPointer.Type()) { targetIndirectValue.Set(mapPointer) } else { targetIndirectValue.Set(mapPointer.Elem()) } } else { targetIndirectValue.Set(newMap) } return err } func (c *Converter) assignConvertedSlice(target, source interface{}, targetIndirectValue reflect.Value, targetIndirectPointerType reflect.Type) error { sliceType := DiscoverTypeByKind(target, reflect.Slice) slicePointer := reflect.New(sliceType) slice := slicePointer.Elem() componentType := DiscoverComponentType(target) var err error ProcessSlice(source, func(item interface{}) bool { var targetComponentPointer = reflect.New(componentType) if componentType.Kind() == reflect.Map { targetComponent := reflect.MakeMap(componentType) targetComponentPointer.Elem().Set(targetComponent) } err = c.AssignConverted(targetComponentPointer.Interface(), item) if err != nil { err = fmt.Errorf("failed to convert slice item from %T to %T, values: from %v to %v, due to %v", item, targetComponentPointer.Interface(), item, targetComponentPointer.Interface(), err) return false } slice.Set(reflect.Append(slice, targetComponentPointer.Elem())) return true }) if targetIndirectPointerType.Kind() == reflect.Slice { targetIndirectValue.Set(slicePointer) } else { targetIndirectValue.Set(slice) } return err } func (c *Converter) assignConvertedStruct(target interface{}, inputMap map[string]interface{}, targetIndirectValue reflect.Value, targetIndirectPointerType reflect.Type) error { newStructPointer := reflect.New(targetIndirectValue.Type()) newStruct := newStructPointer.Elem() fieldsMapping := NewFieldSettingByKey(newStructPointer.Interface(), c.MappedKeyTag) var defaultValueMap = make(map[string]interface{}) var anonymousValueMap map[string]reflect.Value var anonymousFields map[string]reflect.Value for _, value := range fieldsMapping { if defaultValue, ok := value[defaultKey]; ok { var fieldName = value[fieldNameKey] defaultValueMap[fieldName] = defaultValue } if index, ok := value[fieldIndexKey]; ok { if len(anonymousValueMap) == 0 { anonymousValueMap = make(map[string]reflect.Value) anonymousFields = make(map[string]reflect.Value) } field := newStruct.Field(AsInt(index)) if field.Type().Kind() == reflect.Ptr { fieldStruct := reflect.New(field.Type().Elem()) anonymousValueMap[index] = fieldStruct anonymousFields[index] = field } else { anonymousValueMap[index] = field.Addr() anonymousFields[index] = field.Addr() } } } for key, value := range inputMap { aStruct := newStruct mapping, found := fieldsMapping[strings.ToLower(key)] if found { var field reflect.Value fieldName := mapping[fieldNameKey] if fieldIndex, ok := mapping[fieldIndexKey]; ok { var structPointer = anonymousValueMap[fieldIndex] if anonymousFields[fieldIndex].CanAddr() { anonymousFields[fieldIndex].Set(structPointer) } aStruct = structPointer.Elem() initAnonymousStruct(structPointer.Interface()) } field = aStruct.FieldByName(fieldName) fieldType, _ := aStruct.Type().FieldByName(fieldName) if isExported := fieldType.PkgPath == ""; !isExported { structField := &StructField{ Owner: newStructPointer, Value: field, Type: fieldType, } if !onUnexportedHandler(structField) { continue } field = structField.Value } if _, has := defaultValueMap[fieldName]; has { delete(defaultValueMap, fieldName) } previousLayout := c.DateLayout if HasTimeLayout(mapping) { c.DateLayout = GetTimeLayout(mapping) c.DateLayout = previousLayout } if (!field.CanAddr()) && field.Kind() == reflect.Ptr { if err := c.AssignConverted(field.Interface(), value); err != nil { return fmt.Errorf("failed to convert %v to %v due to %v", value, field, err) } } else { if err := c.AssignConverted(field.Addr().Interface(), value); err != nil { return fmt.Errorf("failed to convert %v to %v due to %v", value, field, err) } } if HasTimeLayout(mapping) { c.DateLayout = previousLayout } } } for fieldName, value := range defaultValueMap { field := newStruct.FieldByName(fieldName) err := c.AssignConverted(field.Addr().Interface(), value) if err != nil { return fmt.Errorf("failed to assign default value %v to %v due to %v", value, field, err) } } if targetIndirectPointerType.Kind() == reflect.Slice { targetIndirectValue.Set(newStructPointer) } else { targetIndirectValue.Set(newStruct) } return nil } //customConverter map of target, source type with converter var customConverter = make(map[reflect.Type]map[reflect.Type]func(target, source interface{}) error) //RegisterConverter register custom converter for supplied target, source type func RegisterConverter(target, source reflect.Type, converter func(target, source interface{}) error) { if _, ok := customConverter[target]; !ok { customConverter[target] = make(map[reflect.Type]func(target, source interface{}) error) } customConverter[target][source] = converter } //GetConverter returns register converter for supplied target and source type func GetConverter(target, source interface{}) (func(target, source interface{}) error, bool) { sourceConverters, ok := customConverter[reflect.TypeOf(target)] if !ok { return nil, false } converter, ok := sourceConverters[reflect.TypeOf(source)] return converter, ok } //AssignConverted assign to the target source, target needs to be pointer, input has to be convertible or compatible type func (c *Converter) AssignConverted(target, source interface{}) error { if target == nil { return fmt.Errorf("destination Pointer was nil %v %v", target, source) } if source == nil { return nil } switch targetValuePointer := target.(type) { case *string: switch sourceValue := source.(type) { case string: *targetValuePointer = sourceValue return nil case *string: *targetValuePointer = *sourceValue return nil case []byte: *targetValuePointer = string(sourceValue) return nil case *[]byte: *targetValuePointer = string(NewBytes(*sourceValue)) return nil default: *targetValuePointer = AsString(source) return nil } case **string: switch sourceValue := source.(type) { case string: *targetValuePointer = &sourceValue return nil case *string: *targetValuePointer = sourceValue return nil case []byte: var stringSourceValue = string(sourceValue) *targetValuePointer = &stringSourceValue return nil case *[]byte: var stringSourceValue = string(NewBytes(*sourceValue)) *targetValuePointer = &stringSourceValue return nil default: stringSourceValue := AsString(source) *targetValuePointer = &stringSourceValue return nil } case *[]string: switch sourceValue := source.(type) { case []string: *targetValuePointer = sourceValue return nil case *[]string: *targetValuePointer = *sourceValue return nil case *string: transient := []string{*sourceValue} *targetValuePointer = transient return nil case string: transient := []string{sourceValue} *targetValuePointer = transient return nil default: if IsSlice(source) { var stingItems = make([]string, 0) ProcessSlice(source, func(item interface{}) bool { stingItems = append(stingItems, AsString(item)) return true }) *targetValuePointer = stingItems return nil } else if IsMap(source) { if len(AsMap(source)) == 0 { return nil } } return fmt.Errorf("expected []string but had: %T", source) } case *bool: switch sourceValue := source.(type) { case bool: *targetValuePointer = sourceValue return nil case *bool: *targetValuePointer = *sourceValue return nil case int: *targetValuePointer = sourceValue != 0 return nil case string: boolValue, err := strconv.ParseBool(sourceValue) if err != nil { return err } *targetValuePointer = boolValue return nil case *string: boolValue, err := strconv.ParseBool(*sourceValue) if err != nil { return err } *targetValuePointer = boolValue return nil } case **bool: switch sourceValue := source.(type) { case bool: *targetValuePointer = &sourceValue return nil case *bool: *targetValuePointer = sourceValue return nil case int: boolValue := sourceValue != 0 *targetValuePointer = &boolValue return nil case string: boolValue, err := strconv.ParseBool(sourceValue) if err != nil { return err } *targetValuePointer = &boolValue return nil case *string: boolValue, err := strconv.ParseBool(*sourceValue) if err != nil { return err } *targetValuePointer = &boolValue return nil } case *[]byte: switch sourceValue := source.(type) { case []byte: *targetValuePointer = sourceValue return nil case *[]byte: *targetValuePointer = *sourceValue return nil case string: *targetValuePointer = []byte(sourceValue) return nil case *string: var stringValue = *sourceValue *targetValuePointer = []byte(stringValue) return nil } case **[]byte: switch sourceValue := source.(type) { case []byte: bytes := NewBytes(sourceValue) *targetValuePointer = &bytes return nil case *[]byte: bytes := NewBytes(*sourceValue) *targetValuePointer = &bytes return nil case string: bytes := []byte(sourceValue) *targetValuePointer = &bytes return nil case *string: bytes := []byte(*sourceValue) *targetValuePointer = &bytes return nil } case *int, *int8, *int16, *int32, *int64: directValue := reflect.Indirect(reflect.ValueOf(targetValuePointer)) var intValue, err = ToInt(source) if err != nil { return err } directValue.SetInt(int64(intValue)) return nil case **int, **int8, **int16, **int32, **int64: directType := reflect.TypeOf(targetValuePointer).Elem().Elem() var intValue, err = ToInt(source) if err != nil { if IsNilPointerError(err) { return nil } return err } switch directType.Kind() { case reflect.Int8: alignValue := int8(intValue) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Int16: alignValue := int16(intValue) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Int32: alignValue := int32(intValue) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Int64: alignValue := int64(intValue) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) default: reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&intValue)) } return nil case *uint, *uint8, *uint16, *uint32, *uint64: directValue := reflect.Indirect(reflect.ValueOf(targetValuePointer)) value, err := ToInt(source) if err != nil { return err } directValue.SetUint(uint64(value)) return nil case **uint, **uint8, **uint16, **uint32, **uint64: directType := reflect.TypeOf(targetValuePointer).Elem().Elem() value, err := ToInt(source) if !IsNilPointerError(err) && err != nil { return err } switch directType.Kind() { case reflect.Uint8: alignValue := uint8(value) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Uint16: alignValue := uint16(value) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Uint32: alignValue := uint32(value) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) case reflect.Uint64: alignValue := uint64(value) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&alignValue)) default: reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&value)) } return nil case *float32, *float64: directValue := reflect.Indirect(reflect.ValueOf(targetValuePointer)) value, err := ToFloat(source) if err != nil { return err } directValue.SetFloat(value) return nil case **float32, **float64: directType := reflect.TypeOf(targetValuePointer).Elem().Elem() value, err := ToFloat(source) if err != nil { return err } if directType.Kind() == reflect.Float32 { float32Value := float32(value) reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&float32Value)) } else { reflect.ValueOf(targetValuePointer).Elem().Set(reflect.ValueOf(&value)) } return nil case *time.Time: timeValue, err := ToTime(source, c.DateLayout) if err != nil { return err } *targetValuePointer = *timeValue return nil case **time.Time: timeValue, err := ToTime(source, c.DateLayout) if err != nil { return err } *targetValuePointer = timeValue return nil case *interface{}: if converter, ok := GetConverter(target, source); ok { return converter(target, source) } (*targetValuePointer) = source return nil case **interface{}: if converter, ok := GetConverter(target, source); ok { return converter(target, source) } (*targetValuePointer) = &source return nil default: if converter, ok := GetConverter(target, source); ok { return converter(target, source) } } sourceValue := reflect.ValueOf(source) if source == nil || !sourceValue.IsValid() || (sourceValue.CanSet() && sourceValue.IsNil()) { return nil } targetValue := reflect.ValueOf(target) targetIndirectValue := reflect.Indirect(targetValue) if sourceValue.IsValid() { if sourceValue.Type().AssignableTo(targetValue.Type()) { targetIndirectValue.Set(sourceValue.Elem()) return nil } else if sourceValue.Type().AssignableTo(targetValue.Type().Elem()) && sourceValue.Kind() == targetValue.Type().Elem().Kind() { targetValue.Elem().Set(sourceValue) return nil } } var targetIndirectPointerType = reflect.TypeOf(target).Elem() if targetIndirectPointerType.Kind() == reflect.Ptr || targetIndirectPointerType.Kind() == reflect.Slice || targetIndirectPointerType.Kind() == reflect.Map { targetIndirectPointerType = targetIndirectPointerType.Elem() } if targetIndirectValue.Kind() == reflect.Slice || targetIndirectPointerType.Kind() == reflect.Slice { if sourceValue.Kind() == reflect.Ptr && sourceValue.Elem().Kind() == reflect.Slice { sourceValue = sourceValue.Elem() } if sourceValue.Kind() == reflect.Ptr && sourceValue.IsNil() { return nil } if sourceValue.Kind() == reflect.Slice { if targetIndirectValue.Kind() == reflect.Map { return c.assignConvertedMap(target, source, targetIndirectValue, targetIndirectPointerType) } return c.assignConvertedSlice(target, source, targetIndirectValue, targetIndirectPointerType) } } if targetIndirectValue.Kind() == reflect.Map || targetIndirectPointerType.Kind() == reflect.Map { sourceKind := DereferenceType(sourceValue.Type()).Kind() if sourceKind == reflect.Map { return c.assignConvertedMap(target, source, targetIndirectValue, targetIndirectPointerType) } else if sourceKind == reflect.Struct { if source == nil { return nil } if sourceValue.Kind() == reflect.Ptr && sourceValue.IsNil() { return nil } targetValue := reflect.ValueOf(target) if !targetValue.CanInterface() { return nil } return c.assignConvertedMapFromStruct(source, target, sourceValue) } else if sourceKind == reflect.Slice { componentType := DereferenceType(DiscoverComponentType(source)) if componentType.ConvertibleTo(reflect.TypeOf(keyValue{})) { return c.assignConvertedStructSliceToMap(target, source) } else if componentType.Kind() == reflect.Map { return c.assignConvertedMapSliceToMap(target, source) } else if componentType.Kind() == reflect.Interface { return c.assignConvertedMapSliceToMap(target, source) } } } else if targetIndirectValue.Kind() == reflect.Struct { timeValuePtr := tryExtractTime(target) if timeValuePtr != nil { if timeValue, err := ToTime(source, c.DateLayout); err == nil { return c.assignEmbeddedTime(target, timeValue) } } sourceMap, err := ToMap(source) if err != nil { return fmt.Errorf("unable to convert %T to %T", source, target) } return c.assignConvertedStruct(target, sourceMap, targetIndirectValue, targetIndirectPointerType) } else if targetIndirectPointerType.Kind() == reflect.Struct { structPointer := reflect.New(targetIndirectPointerType) inputMap, err := ToMap(source) if err != nil { return fmt.Errorf("unable transfer to %T, source should be a map but was %T(%v)", target, source, source) } if err = c.assignConvertedStruct(target, inputMap, structPointer.Elem(), targetIndirectPointerType); err != nil { return err } targetIndirectValue.Set(structPointer) return nil } if sourceValue.IsValid() && sourceValue.Type().AssignableTo(targetIndirectValue.Type()) { targetIndirectValue.Set(sourceValue) return nil } if sourceValue.IsValid() && sourceValue.Type().ConvertibleTo(targetIndirectValue.Type()) { converted := sourceValue.Convert(targetIndirectValue.Type()) targetIndirectValue.Set(converted) return nil } targetDeRefType := DereferenceType(target) for _, candidate := range numericTypes { if candidate.Kind() == targetDeRefType.Kind() { var pointerCount = CountPointers(target) var compatibleTarget = reflect.New(candidate) for i := 0; i < pointerCount-1; i++ { compatibleTarget = reflect.New(compatibleTarget.Type()) } if err := c.AssignConverted(compatibleTarget.Interface(), source); err == nil { targetValue := reflect.ValueOf(target) targetValue.Elem().Set(compatibleTarget.Elem().Convert(targetValue.Elem().Type())) return nil } } } return fmt.Errorf("Unable to convert type %T into type %T\n\t%v", source, target, source) } func (c *Converter) assignEmbeddedTime(target interface{}, source *time.Time) error { targetValue := reflect.ValueOf(target) structValue := targetValue if targetValue.Kind() == reflect.Ptr { structValue = reflect.Indirect(targetValue) } anonymous := structValue.Field(0) anonymous.Set(reflect.ValueOf(*source)) if targetValue.Kind() == reflect.Ptr { targetValue.Elem().Set(structValue) } return nil } type keyValue struct { Key, Value interface{} } func (c *Converter) assignConvertedStructSliceToMap(target, source interface{}) (err error) { mapType := DiscoverTypeByKind(target, reflect.Map) mapPointer := reflect.ValueOf(target) mapValueType := mapType.Elem() mapKeyType := mapType.Key() newMap := mapPointer.Elem() newMap.Set(reflect.MakeMap(mapType)) keyValueType := reflect.TypeOf(keyValue{}) ProcessSlice(source, func(item interface{}) bool { if item == nil { return true } item = reflect.ValueOf(DereferenceValue(item)).Convert(keyValueType).Interface() pair, ok := item.(keyValue) if !ok { return true } targetMapValuePointer := reflect.New(mapValueType) err = c.AssignConverted(targetMapValuePointer.Interface(), pair.Value) if err != nil { return false } targetMapKeyPointer := reflect.New(mapKeyType) err = c.AssignConverted(targetMapKeyPointer.Interface(), pair.Key) if err != nil { return false } var elementKey = targetMapKeyPointer.Elem() var elementValue = targetMapValuePointer.Elem() if elementKey.Type() != mapKeyType { if elementKey.Type().AssignableTo(mapKeyType) { elementKey = elementKey.Convert(mapKeyType) } } if !elementValue.Type().AssignableTo(newMap.Type().Elem()) { var compatibleValue = reflect.New(newMap.Type().Elem()) err = c.AssignConverted(compatibleValue.Interface(), elementValue.Interface()) if err != nil { return false } elementValue = compatibleValue.Elem() } newMap.SetMapIndex(elementKey, elementValue) return true }) return err } //entryMapToKeyValue converts entry map into map func entryMapToKeyValue(entryMap map[string]interface{}) (key string, value interface{}, err error) { if len(entryMap) > 2 { return key, value, fmt.Errorf("map entry needs to have 2 elements but had: %v, %v", len(entryMap), entryMap) } hasValue := false for k, v := range entryMap { if strings.ToLower(k) == "key" { key = AsString(v) continue } else if strings.ToLower(k) == "value" { hasValue = true value = v } } if key == "" { return key, value, fmt.Errorf("key is required in entryMap %v", entryMap) } if !hasValue && len(entryMap) == 2 { return key, value, fmt.Errorf("map entry needs to have key, value pair but had: %v", entryMap) } return key, value, nil } func (c *Converter) assignConvertedMapSliceToMap(target, source interface{}) (err error) { mapType := DiscoverTypeByKind(target, reflect.Map) mapPointer := reflect.ValueOf(target) mapValueType := mapType.Elem() mapKeyType := mapType.Key() newMap := mapPointer.Elem() newMap.Set(reflect.MakeMap(mapType)) ProcessSlice(source, func(item interface{}) bool { if item == nil { return true } entryMap := AsMap(item) key, value, e := entryMapToKeyValue(entryMap) if e != nil { err = fmt.Errorf("unable to cast %T to %T", source, target) return false } targetMapValuePointer := reflect.New(mapValueType) err = c.AssignConverted(targetMapValuePointer.Interface(), value) if err != nil { return false } targetMapKeyPointer := reflect.New(mapKeyType) err = c.AssignConverted(targetMapKeyPointer.Interface(), key) if err != nil { return false } var elementKey = targetMapKeyPointer.Elem() var elementValue = targetMapValuePointer.Elem() if elementKey.Type() != mapKeyType { if elementKey.Type().AssignableTo(mapKeyType) { elementKey = elementKey.Convert(mapKeyType) } } if !elementValue.Type().AssignableTo(newMap.Type().Elem()) { var compatibleValue = reflect.New(newMap.Type().Elem()) err = c.AssignConverted(compatibleValue.Interface(), elementValue.Interface()) if err != nil { return false } elementValue = compatibleValue.Elem() } newMap.SetMapIndex(elementKey, elementValue) return true }) return err } func (c *Converter) assignConvertedMapFromStruct(source, target interface{}, sourceValue reflect.Value) error { if source == nil || !sourceValue.IsValid() { return nil } targetMap := AsMap(target) if targetMap == nil { return fmt.Errorf("target %T is not a map", target) } return ProcessStruct(source, func(fieldType reflect.StructField, field reflect.Value) error { if !field.CanInterface() { return nil } value := field.Interface() if value == nil { return nil } if timeVal := tryExtractTime(value); timeVal != nil { value = timeVal.Format(time.RFC3339) } var fieldTarget interface{} if IsStruct(value) { aMap := make(map[string]interface{}) if err := c.AssignConverted(&aMap, value); err != nil { return err } fieldTarget = aMap } else if IsSlice(value) { var componentType = DereferenceType(DiscoverComponentType(value)) if componentType.Kind() == reflect.Struct { var slice = make([]map[string]interface{}, 0) if err := c.AssignConverted(&slice, value); err != nil { return err } fieldTarget = slice } else { if _, isByteArray := value.([]byte); isByteArray { fieldTarget = value } else { var slice = make([]interface{}, 0) if err := c.AssignConverted(&slice, value); err != nil { return err } fieldTarget = slice } } } else if err := c.AssignConverted(&fieldTarget, value); err != nil { return err } fieldName := fieldType.Name keyTag := strings.Trim(fieldType.Tag.Get(c.MappedKeyTag), `"`) if keyTag != "" { key := strings.Split(keyTag, ",")[0] if key == "-" { return nil } fieldName = key } targetMap[fieldName] = fieldTarget return nil }) } func tryExtractTime(value interface{}) *time.Time { if timeVal, ok := value.(time.Time); ok { return &timeVal } if timeVal, ok := value.(*time.Time); ok && timeVal != nil { return timeVal } if !IsStruct(value) { return nil } structOrPtrValue := reflect.ValueOf(value) structValue := structOrPtrValue if structOrPtrValue.Kind() == reflect.Ptr { structValue = reflect.Indirect(structOrPtrValue) } if structValue.Kind() == reflect.Ptr { structValue = reflect.ValueOf(DereferenceValue(structValue.Interface())) } if !structValue.IsValid() { return nil } if structValue.NumField() > 1 { return nil } timeField, ok := structValue.Type().FieldByName("Time") if !ok || !timeField.Anonymous { return nil } timeValue := structValue.Field(timeField.Index[0]) if timeValue.CanAddr() { return tryExtractTime(timeValue.Addr().Interface()) } return tryExtractTime(timeValue.Interface()) } //NewColumnConverter create a new converter, that has ability to convert map to struct using column mapping func NewColumnConverter(dateLayout string) *Converter { return &Converter{dateLayout, "column"} } //NewConverter create a new converter, that has ability to convert map to struct, it uses keytag to identify source and dest of fields/keys func NewConverter(dateLayout, keyTag string) *Converter { if keyTag == "" { keyTag = "name" } return &Converter{dateLayout, keyTag} } //DefaultConverter represents a default data structure converter var DefaultConverter = NewConverter("", "name") //DereferenceValues replaces pointer to its value within a generic map or slice func DereferenceValues(source interface{}) interface{} { if IsMap(source) { var aMap = make(map[string]interface{}) _ = ProcessMap(source, func(key, value interface{}) bool { if value == nil { return true } aMap[AsString(key)] = DereferenceValue(value) return true }) return aMap } else if IsSlice(source) { var aSlice = make([]interface{}, 0) ProcessSlice(source, func(item interface{}) bool { aSlice = append(aSlice, DereferenceValue(item)) return true }) return aSlice } return DereferenceValue(source) } //DereferenceValue dereference passed in value func DereferenceValue(value interface{}) interface{} { if value == nil { return nil } var reflectValue reflect.Value switch actualValue := value.(type) { case reflect.Value: reflectValue = actualValue default: reflectValue = reflect.ValueOf(value) } for { if !reflectValue.IsValid() { break } if !reflectValue.CanInterface() { break } if reflectValue.Type().Kind() != reflect.Ptr { break } reflectValue = reflectValue.Elem() } var result interface{} if reflectValue.IsValid() && reflectValue.CanInterface() { result = reflectValue.Interface() } if result != nil && (IsMap(result) || IsSlice(result)) { return DereferenceValues(value) } return result } //DereferenceType dereference passed in value func DereferenceType(value interface{}) reflect.Type { if value == nil { return nil } var reflectType reflect.Type reflectValue, ok := value.(reflect.Value) if ok { reflectType = reflectValue.Type() } else if reflectType, ok = value.(reflect.Type); !ok { reflectType = reflect.TypeOf(value) } for { if reflectType.Kind() != reflect.Ptr { break } reflectType = reflectType.Elem() } return reflectType } //CountPointers count pointers to undelying non pointer type func CountPointers(value interface{}) int { if value == nil { return 0 } var result = 0 reflectType, ok := value.(reflect.Type) if !ok { reflectType = reflect.TypeOf(value) } for { if reflectType.Kind() != reflect.Ptr { break } result++ reflectType = reflectType.Elem() } return result } func initAnonymousStruct(aStruct interface{}) { structValue := DiscoverValueByKind(reflect.ValueOf(aStruct), reflect.Struct) structType := structValue.Type() for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if !fieldType.Anonymous { continue } field := structValue.Field(i) if !IsStruct(field) { continue } var aStruct interface{} if fieldType.Type.Kind() == reflect.Ptr { if field.IsNil() { if !field.CanSet() { continue } structValue.Field(i).Set(reflect.New(fieldType.Type.Elem())) } aStruct = field.Interface() } else { if !field.CanAddr() { continue } aStruct = field.Addr().Interface() } initAnonymousStruct(aStruct) } } toolbox-0.33.2/converter_test.go000066400000000000000000000362231374110251100166630ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "reflect" "testing" "time" ) func TestConverter(t *testing.T) { converter := toolbox.NewColumnConverter(toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z")) { type A1 struct { K1 int } type A2 struct { K2 int } type C struct { *A1 *A2 K3 int } aMap := map[string]interface{}{ "K1": 1, "K2": 20, "K3": 30, } c := C{} err := converter.AssignConverted(&c, aMap) assert.Nil(t, err) assert.Equal(t, 1, c.K1) assert.Equal(t, 20, c.K2) assert.Equal(t, 30, c.K3) } { target := make([]interface{}, 1) err := converter.AssignConverted(&target[0], nil) assert.Nil(t, err) assert.Nil(t, target[0]) } { err := converter.AssignConverted(nil, nil) assert.NotNil(t, err) } { var value interface{} var test = 123 err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, 123, *(value.(*int))) } { var value interface{} var test = 123 err := converter.AssignConverted(&value, test) assert.Nil(t, err) assert.Equal(t, 123, value.(int)) } { //Byte types { var value []byte var test = []byte("abc") err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", string(value)) } { var value []byte var test = "abc" err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", string(value)) } { var value []byte var test = []byte("abc") err := converter.AssignConverted(&value, test) assert.Nil(t, err) assert.Equal(t, "abc", string(value)) } { var value []byte var test = "abc" err := converter.AssignConverted(&value, test) assert.Nil(t, err) assert.Equal(t, "abc", string(value)) } } { //Byte types { var value *[]byte var test = []byte("abc") err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", string(*value)) } { var value *[]byte var test = "abc" err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", string(*value)) } { var value *[]byte var test = []byte("abc") err := converter.AssignConverted(&value, test) assert.Nil(t, err) assert.Equal(t, "abc", string(*value)) } { var value *[]byte var test = "abc" err := converter.AssignConverted(&value, test) assert.Nil(t, err) assert.Equal(t, "abc", string(*value)) } } { var value string err := converter.AssignConverted(&value, "abc") assert.Nil(t, err) assert.Equal(t, "abc", value) } { var value string var test = "abc" err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", value) } { var value string var test = 12 err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "12", value) } { var value *string var test = 12 err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "12", *value) } { var value *string err := converter.AssignConverted(&value, "abc") assert.Nil(t, err) assert.Equal(t, "abc", *value) } { var value *string var test = "abc" err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", *value) } { var value string err := converter.AssignConverted(&value, []byte("abc")) assert.Nil(t, err) assert.Equal(t, "abc", value) } { var value string var test = []byte("abc") err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", value) } { var value *string err := converter.AssignConverted(&value, []byte("abc")) assert.Nil(t, err) assert.Equal(t, "abc", *value) } { var value *string var test = []byte("abc") err := converter.AssignConverted(&value, &test) assert.Nil(t, err) assert.Equal(t, "abc", *value) } { var value float64 for _, item := range []interface{}{int(102), int64(102), float64(102), float32(102), "102"} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.Equal(t, float64(102), value) } } { var value int64 for _, item := range []interface{}{int(102), int64(102), float64(102), float32(102), "102"} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.Equal(t, int64(102), value) } } { var value *int64 for _, item := range []interface{}{int(102), int64(102), float64(102), float32(102), "102"} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.Equal(t, int64(102), *value) } } { var value uint64 for _, item := range []interface{}{int(102), int64(102), float64(102), float32(102), "102"} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.Equal(t, uint64(102), value) } } { var value *uint64 for _, item := range []interface{}{int(102), int64(102), float64(102), float32(102), "102"} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.Equal(t, uint64(102), *value) } } { var value *float64 var testData = []interface{}{int(102), int64(102), float64(102), float32(102), "102"} for _, item := range testData { err := converter.AssignConverted(&value, item) if assert.Nil(t, err) { if assert.NotNil(t, value) { assert.Equal(t, float64(102), *value) } } } } { var value *bool sTrue := "true" vTrue := true for _, item := range []interface{}{1, true, "true", &sTrue, &vTrue} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.True(t, *value) } err := converter.AssignConverted(&value, "abc") assert.NotNil(t, err) } { var value bool sTrue := "true" vTrue := true for _, item := range []interface{}{1, true, "true", &sTrue, &vTrue} { err := converter.AssignConverted(&value, item) assert.Nil(t, err) assert.True(t, value) } err := converter.AssignConverted(&value, "abc") assert.NotNil(t, err) } { var value *time.Time date := "2016-02-22 12:32:01 UTC" { err := converter.AssignConverted(&value, date) if assert.Nil(t, err) { assert.Equal(t, 1456144321, int(value.Unix())) } } { err := converter.AssignConverted(&value, &date) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { err := converter.AssignConverted(&value, "1456144321") assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { var unixNano = "1513271472347277824" err := converter.AssignConverted(&value, unixNano) assert.Nil(t, err) assert.Equal(t, 1513271472, int(value.Unix())) } { var unixNano = "1456144321001" err := converter.AssignConverted(&value, unixNano) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := "1456144321.0" err := converter.AssignConverted(&value, unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := 1456144321.0 err := converter.AssignConverted(&value, unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := 1456144321.0 err := converter.AssignConverted(&value, &unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { date := "2016/02/22 12:32:01 UTC" err := converter.AssignConverted(&value, date) assert.NotNil(t, err, "invalid date format") } } { var value time.Time date := "2016-02-22 12:32:01 UTC" { err := converter.AssignConverted(&value, date) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { err := converter.AssignConverted(&value, &date) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { err := converter.AssignConverted(&value, "1456144321") assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := "1456144321.0" err := converter.AssignConverted(&value, unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := 1456144321.0 err := converter.AssignConverted(&value, unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { unix := 1456144321.0 err := converter.AssignConverted(&value, &unix) assert.Nil(t, err) assert.Equal(t, 1456144321, int(value.Unix())) } { date := "2016/02/22 12:32:01 UTC" err := converter.AssignConverted(&value, date) assert.NotNil(t, err, "invalid date format") } //{ // unix := 1668069210749 // err := converter.AssignConverted(&value, &unix) // assert.Nil(t, err) // assert.Equal(t, 1668069210, int(value.Unix())) //} } { type A struct { Id int Name string A []string } aMap := map[string]interface{}{ "Id": 1, "Name": "abc", "A": []string{"a", "b"}, } a := A{} err := converter.AssignConverted(&a, aMap) assert.Nil(t, err) assert.Equal(t, 1, a.Id) assert.Equal(t, "abc", a.Name) assert.Equal(t, 2, len(a.A)) } { aMap := map[string]interface{}{ "Id": 1, "Name": "abc", "A": []string{"a", "b"}, } { a := make(map[string]interface{}) err := converter.AssignConverted(&a, aMap) assert.Nil(t, err) assert.Equal(t, 1, a["Id"]) assert.Equal(t, "abc", a["Name"]) } { a := make(map[string]interface{}) err := converter.AssignConverted(&a, &aMap) assert.Nil(t, err) assert.Equal(t, 1, a["Id"]) assert.Equal(t, "abc", a["Name"]) } } { aSlice := []interface{}{1, 2, 3} target := make([]int, 0) err := converter.AssignConverted(&target, aSlice) assert.Nil(t, err) assert.Equal(t, 1, target[0]) assert.Equal(t, 3, len(target)) } { aSlice := []interface{}{1, 2, 3} target := make([]int, 0) err := converter.AssignConverted(&target, aSlice) assert.Nil(t, err) assert.Equal(t, 1, target[0]) assert.Equal(t, 3, len(target)) } } func Test_Converter_SliceToMap(t *testing.T) { //KeyValue represents sorted map entry type KeyValue struct { Key, Value interface{} } { converter := toolbox.NewColumnConverter(toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z")) var slice = []*KeyValue{ {Key: "k1", Value: 1}, {Key: "k2", Value: 2}, } var aMap = make(map[string]interface{}) err := converter.AssignConverted(&aMap, slice) assert.Nil(t, err) assert.EqualValues(t, map[string]interface{}{ "k1": 1, "k2": 2, }, aMap) } { converter := toolbox.NewColumnConverter(toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z")) var slice = []map[string]interface{}{ {"Key": "k1", "Value": 1}, {"Key": "k2", "Value": 2}, } var aMap = make(map[string]interface{}) err := converter.AssignConverted(&aMap, slice) assert.Nil(t, err) assert.EqualValues(t, map[string]interface{}{ "k1": 1, "k2": 2, }, aMap) } } func TestAsString(t *testing.T) { assert.Equal(t, "abc", toolbox.AsString(([]byte)("abc"))) assert.Equal(t, "123", toolbox.AsString("123")) var aInt uint = 1 assert.Equal(t, "1", toolbox.AsString(aInt)) type S struct { Id int } assert.Equal(t, "&{1}", toolbox.AsString(&S{1})) { var bytes = []uint8{ 34, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 34, } assert.EqualValues(t, `"Hello World"`, toolbox.AsString(bytes)) } { var bytes = []interface{}{ uint8(34), uint8(72), uint8(101), uint8(108), uint8(108), uint8(111), uint8(32), uint8(87), uint8(111), uint8(114), uint8(108), uint8(100), uint8(34), } assert.EqualValues(t, `"Hello World"`, toolbox.AsString(bytes)) } } func TestAsFloat(t *testing.T) { assert.Equal(t, 1.1, toolbox.AsFloat(1.1)) assert.Equal(t, 0.0, toolbox.AsFloat("abc")) } func TestAsBoolean(t *testing.T) { assert.False(t, toolbox.AsBoolean(1.1)) assert.True(t, toolbox.AsBoolean("true")) assert.True(t, toolbox.AsBoolean(0x1)) assert.False(t, toolbox.AsBoolean(0x0)) } func TestAsInt(t *testing.T) { assert.Equal(t, 1, toolbox.AsInt(1.1)) assert.Equal(t, 0, toolbox.AsInt("avc")) } func TestDiscoverValueAndKind(t *testing.T) { { value, kind := toolbox.DiscoverValueAndKind("true") assert.Equal(t, true, value) assert.Equal(t, reflect.Bool, kind) } { value, kind := toolbox.DiscoverValueAndKind("abc") assert.Equal(t, "abc", value) assert.Equal(t, reflect.String, kind) } { value, kind := toolbox.DiscoverValueAndKind("3.4") assert.Equal(t, 3.4, value) assert.Equal(t, reflect.Float64, kind) } { value, kind := toolbox.DiscoverValueAndKind("3") assert.Equal(t, 3, value) assert.Equal(t, reflect.Int, kind) } { value, kind := toolbox.DiscoverValueAndKind("") assert.Nil(t, value) assert.Equal(t, reflect.Invalid, kind) } } func TestDiscoverCollectionValuesAndKind(t *testing.T) { { values, kind := toolbox.DiscoverCollectionValuesAndKind([]interface{}{ 1, 2.3, "abc", }) assert.Equal(t, reflect.String, kind) assert.Equal(t, "1", values[0]) assert.Equal(t, "2.3", values[1]) assert.Equal(t, "abc", values[2]) } { values, kind := toolbox.DiscoverCollectionValuesAndKind([]interface{}{ 1, 2.3, }) assert.Equal(t, reflect.Float64, kind) assert.Equal(t, 1.0, values[0]) assert.Equal(t, 2.3, values[1]) } { values, kind := toolbox.DiscoverCollectionValuesAndKind([]interface{}{ "true", false, }) assert.Equal(t, reflect.Bool, kind) assert.Equal(t, true, values[0]) assert.Equal(t, false, values[1]) } } func TestDiscoverCollectionValueType(t *testing.T) { { var input = []string{"3.2", "1.2"} var output, kind = toolbox.DiscoverCollectionValuesAndKind(input) assert.Equal(t, reflect.Float64, kind) assert.Equal(t, 1.2, output[1]) } { var input = []string{"3.2", "abc"} var output, kind = toolbox.DiscoverCollectionValuesAndKind(input) assert.Equal(t, reflect.String, kind) assert.Equal(t, "abc", output[1]) assert.Equal(t, "3.2", output[0]) } } func TestUnwrapValue(t *testing.T) { type S struct { F1 int F2 float64 F3 string F4 uint } s := S{1, 1.1, "a", uint(1)} sValue := reflect.ValueOf(s) { fieldValue := sValue.FieldByName("F1") value := toolbox.UnwrapValue(&fieldValue) assert.Equal(t, 1, value) } { fieldValue := sValue.FieldByName("F2") value := toolbox.UnwrapValue(&fieldValue) assert.Equal(t, 1.1, value) } { fieldValue := sValue.FieldByName("F3") value := toolbox.UnwrapValue(&fieldValue) assert.Equal(t, "a", value) } } func TestConverter_AsInt(t *testing.T) { intValue := toolbox.AsInt("5.638679022673832e+18") assert.True(t, intValue > 0) } func TestConvertedMapFromStruct(t *testing.T) { var aStruct = struct { ID int `json:"id"` Name string `json:"name"` Description string }{1, "test", "desc"} converter := toolbox.NewConverter("", "json") var target = make(map[string]interface{}) err := converter.AssignConverted(&target, aStruct) assert.Nil(t, err) assert.EqualValues(t, map[string]interface{}{ "id": 1, "name": "test", "Description": "desc", }, target) } func TestConvertedSliceToMapError(t *testing.T) { aSlice := []map[string]interface{}{ { "id": 1, "name": 111, }, { "id": 2, "name": 222, }, } var aMap = make(map[string]interface{}) converter := toolbox.NewConverter("", "json") err := converter.AssignConverted(&aMap, aSlice) assert.NotNil(t, err) } toolbox-0.33.2/cred/000077500000000000000000000000001374110251100141755ustar00rootroot00000000000000toolbox-0.33.2/cred/blowfish.go000066400000000000000000000026401374110251100163430ustar00rootroot00000000000000package cred import ( "crypto/cipher" "golang.org/x/crypto/blowfish" ) func blowfishChecksizeAndPad(padded []byte) []byte { modulus := len(padded) % blowfish.BlockSize if modulus != 0 { padlen := blowfish.BlockSize - modulus for i := 0; i < padlen; i++ { padded = append(padded, 0) } } return padded } type blowfishCipher struct { cipher *blowfish.Cipher } func (b *blowfishCipher) Encrypt(source []byte) []byte { paddedSource := blowfishChecksizeAndPad(source) ciphertext := make([]byte, blowfish.BlockSize+len(paddedSource)) eiv := ciphertext[:blowfish.BlockSize] encodedBlackEncryptor := cipher.NewCBCEncrypter(b.cipher, eiv) encodedBlackEncryptor.CryptBlocks(ciphertext[blowfish.BlockSize:], paddedSource) return ciphertext } func (b *blowfishCipher) Decrypt(encrypted []byte) []byte { div := encrypted[:blowfish.BlockSize] decrypted := encrypted[blowfish.BlockSize:] if len(decrypted)%blowfish.BlockSize != 0 { panic("decrypted is not a multiple of blowfish.BlockSize") } dcbc := cipher.NewCBCDecrypter(b.cipher, div) dcbc.CryptBlocks(decrypted, decrypted) var result = make([]byte, 0) for _, b := range decrypted { if b == 0x0 { break } result = append(result, b) } return result } func NewBlowfishCipher(key []byte) (Cipher, error) { var passwordCipher, err = blowfish.NewCipher(key) if err != nil { return nil, err } return &blowfishCipher{ cipher: passwordCipher, }, nil } toolbox-0.33.2/cred/blowfish_test.go000066400000000000000000000021541374110251100174020ustar00rootroot00000000000000package cred_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox/cred" "testing" ) func TestNewBlowfishCipher(t *testing.T) { cipher, err := cred.NewBlowfishCipher(cred.DefaultKey) if assert.Nil(t, err) { { var secret = "This is secret pass12312312321" encrypted := cipher.Encrypt([]byte(secret)) decrypted := cipher.Decrypt(encrypted) assert.Equal(t, secret, string(decrypted)) } { var secret = "abc" encrypted := cipher.Encrypt([]byte(secret)) decrypted := cipher.Decrypt(encrypted) assert.Equal(t, secret, string(decrypted)) } { var secret = "123!abc" encrypted := cipher.Encrypt([]byte(secret)) decrypted := cipher.Decrypt(encrypted) assert.Equal(t, secret, string(decrypted)) } { var secret = "test123@423 #!424" encrypted := cipher.Encrypt([]byte(secret)) decrypted := cipher.Decrypt(encrypted) assert.Equal(t, secret, string(decrypted)) } { var secret = "test123@423 #!424" encrypted := cipher.Encrypt([]byte(secret)) decrypted := cipher.Decrypt(encrypted) assert.Equal(t, secret, string(decrypted)) } } } toolbox-0.33.2/cred/cipher.go000066400000000000000000000002621374110251100157760ustar00rootroot00000000000000package cred type Encryptor interface { Encrypt(src []byte) []byte } type Decryptor interface { Decrypt(src []byte) []byte } type Cipher interface { Encryptor Decryptor } toolbox-0.33.2/cred/config.go000066400000000000000000000164641374110251100160040ustar00rootroot00000000000000package cred import ( "bytes" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "github.com/viant/toolbox" "golang.org/x/crypto/ssh" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" "gopkg.in/yaml.v2" "io" "io/ioutil" "os" "path" "strings" ) var sshKeyFileCandidates = []string{"/.ssh/id_rsa", "/.ssh/id_dsa"} var DefaultKey = []byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D} var PasswordCipher = GetDefaultPasswordCipher() type Config struct { Username string `json:",omitempty"` Email string `json:",omitempty"` Password string `json:",omitempty"` EncryptedPassword string `json:",omitempty"` Endpoint string `json:",omitempty"` PrivateKeyPath string `json:",omitempty"` PrivateKeyPassword string `json:",omitempty"` PrivateKeyEncryptedPassword string `json:",omitempty"` //amazon cloud credential Key string `json:",omitempty"` Secret string `json:",omitempty"` Region string `json:",omitempty"` AccountID string `json:",omitempty"` Token string `json:",omitempty"` //google cloud credential ClientEmail string `json:"client_email,omitempty"` TokenURL string `json:"token_uri,omitempty"` PrivateKey string `json:"private_key,omitempty"` PrivateKeyID string `json:"private_key_id,omitempty"` ProjectID string `json:"project_id,omitempty"` TokenURI string `json:"token_uri"` Type string `json:"type"` ClientX509CertURL string `json:"client_x509_cert_url"` AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"` //JSON string for this secret Data string `json:",omitempty"` sshClientConfig *ssh.ClientConfig jwtClientConfig *jwt.Config } func (c *Config) Load(filename string) error { reader, err := toolbox.OpenFile(filename) if err != nil { return err } defer reader.Close() ext := path.Ext(filename) return c.LoadFromReader(reader, ext) } func (c *Config) LoadFromReader(reader io.Reader, ext string) error { if strings.Contains(ext, "yaml") || strings.Contains(ext, "yml") { var data, err = ioutil.ReadAll(reader) if err != nil { return err } err = yaml.Unmarshal(data, c) if err != nil { return err } } else { err := json.NewDecoder(reader).Decode(c) if err != nil { return nil } } if c.EncryptedPassword != "" { decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(c.EncryptedPassword)) data, err := ioutil.ReadAll(decoder) if err != nil { return err } c.Password = string(PasswordCipher.Decrypt(data)) } else if c.Password != "" { c.encryptPassword(c.Password) } if c.PrivateKeyEncryptedPassword != "" { decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(c.PrivateKeyEncryptedPassword)) data, err := ioutil.ReadAll(decoder) if err != nil { return err } c.PrivateKeyPassword = string(PasswordCipher.Decrypt(data)) } return nil } func (c *Config) Save(filename string) error { _ = os.Remove(filename) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) if err != nil { return err } defer file.Close() return c.Write(file) } func (c *Config) Write(writer io.Writer) error { var password = c.Password defer func() { c.Password = password }() if password != "" { c.encryptPassword(password) c.Password = "" } return json.NewEncoder(writer).Encode(c) } func (c *Config) encryptPassword(password string) { encrypted := PasswordCipher.Encrypt([]byte(password)) buf := new(bytes.Buffer) encoder := base64.NewEncoder(base64.StdEncoding, buf) defer encoder.Close() encoder.Write(encrypted) encoder.Close() c.EncryptedPassword = string(buf.Bytes()) } func (c *Config) applyDefaultIfNeeded() { if c.Username == "" { c.Username = os.Getenv("USER") } if c.PrivateKeyPath == "" && c.Password == "" { homeDirectory := os.Getenv("HOME") if homeDirectory != "" { for _, candidate := range sshKeyFileCandidates { filename := path.Join(homeDirectory, candidate) file, err := os.Open(filename) if err == nil { file.Close() c.PrivateKeyPath = filename break } } } } } //IsKeyEncrypted checks if supplied key content is encrypyed by password func IsKeyEncrypted(keyPath string) bool { privateKeyBytes, err := ioutil.ReadFile(keyPath) if err != nil { return false } block, _ := pem.Decode(privateKeyBytes) if block == nil { return false } return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") } //SSHClientConfig returns a new instance of sshClientConfig func (c *Config) SSHClientConfig() (*ssh.ClientConfig, error) { return c.ClientConfig() } //NewJWTConfig returns new JWT config for supplied scopes func (c *Config) NewJWTConfig(scopes ...string) (*jwt.Config, error) { var result = &jwt.Config{ Email: c.ClientEmail, Subject: c.ClientEmail, PrivateKey: []byte(c.PrivateKey), PrivateKeyID: c.PrivateKeyID, Scopes: scopes, TokenURL: c.TokenURL, } if c.PrivateKeyPath != "" && c.PrivateKey == "" { privateKey, err := ioutil.ReadFile(c.PrivateKeyPath) if err != nil { return nil, fmt.Errorf("failed to open provide key: %v, %v", c.PrivateKeyPath, err) } result.PrivateKey = privateKey } if result.TokenURL == "" { result.TokenURL = google.JWTTokenURL } return result, nil } //JWTConfig returns jwt config and projectID func (c *Config) JWTConfig(scopes ...string) (config *jwt.Config, projectID string, err error) { config, err = c.NewJWTConfig(scopes...) return config, c.ProjectID, err } func loadPEM(location string, password string) ([]byte, error) { var pemBytes []byte if IsKeyEncrypted(location) { block, _ := pem.Decode(pemBytes) if block == nil { return nil, errors.New("invalid PEM data") } if x509.IsEncryptedPEMBlock(block) { key, err := x509.DecryptPEMBlock(block, []byte(password)) if err != nil { return nil, err } block = &pem.Block{Type: block.Type, Bytes: key} pemBytes = pem.EncodeToMemory(block) return pemBytes, nil } } return ioutil.ReadFile(location) } //ClientConfig returns a new instance of sshClientConfig func (c *Config) ClientConfig() (*ssh.ClientConfig, error) { if c.sshClientConfig != nil { return c.sshClientConfig, nil } c.applyDefaultIfNeeded() result := &ssh.ClientConfig{ User: c.Username, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Auth: make([]ssh.AuthMethod, 0), } if c.Password != "" { result.Auth = append(result.Auth, ssh.Password(c.Password)) } if c.PrivateKeyPath != "" { password := c.PrivateKeyPassword //backward-compatible if password == "" { password = c.Password } pemBytes, err := loadPEM(c.PrivateKeyPath, password) key, err := ssh.ParsePrivateKey(pemBytes) if err != nil { return nil, err } result.Auth = append(result.Auth, ssh.PublicKeys(key)) } c.sshClientConfig = result return result, nil } //NewConfig create a new config for supplied file name func NewConfig(filename string) (*Config, error) { var config = &Config{} err := config.Load(filename) if err != nil { return nil, err } config.applyDefaultIfNeeded() return config, nil } //GetDefaultPasswordCipher return a default password cipher func GetDefaultPasswordCipher() Cipher { var result, err = NewBlowfishCipher(DefaultKey) if err != nil { return nil } return result } toolbox-0.33.2/cred/config_test.go000066400000000000000000000023141374110251100170300ustar00rootroot00000000000000package cred_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox/cred" "io/ioutil" "os" "path" "strings" "testing" ) func TestConfig_Load(t *testing.T) { var tempDir = os.TempDir() var testFile = path.Join(tempDir, "credTest1.json") _ = os.Remove(testFile) var data = "{\"Username\":\"adrian\", \"Password\":\"abc\"}" err := ioutil.WriteFile(testFile, []byte(data), 0644) assert.Nil(t, err) { config, err := cred.NewConfig(testFile) assert.Nil(t, err) assert.Equal(t, "abc", config.Password) assert.Equal(t, "adrian", config.Username) assert.Equal(t, "AAAAAAAAAAAXUPcVbxwWlQ==", config.EncryptedPassword) _ = os.Remove(testFile) config.Save(testFile) } { config, err := cred.NewConfig(testFile) assert.Nil(t, err) assert.Equal(t, "abc", config.Password) assert.Equal(t, "adrian", config.Username) assert.Equal(t, "AAAAAAAAAAAXUPcVbxwWlQ==", config.EncryptedPassword) } { configJSON := `{"Username":"adrian","EncryptedPassword":"AAAAAAAAAAAXUPcVbxwWlQ=="}` config := cred.Config{} err = config.LoadFromReader(strings.NewReader(configJSON), ".json") assert.Nil(t, err) assert.EqualValues(t, "abc", config.Password) } _ = os.Remove(testFile) } toolbox-0.33.2/data/000077500000000000000000000000001374110251100141715ustar00rootroot00000000000000toolbox-0.33.2/data/README.md000066400000000000000000000064201374110251100154520ustar00rootroot00000000000000# Data utilities ### Expandable Map & Collection Expandable structure enable nested data structure to substitution source for other data structure or text. Data substitution expression starts with $ sign, you can use path where dot or [index] allows access data sub node. - [ExpandAsText](#ExpandAsText) - [Expand](#Expand) ### ExpandAsText ExpandAsText expands any expression that has satisfied dependencies, meaning only expression that path is present can be expanded, otherwise expression is left unchanged. In case when UDF is used, expression is expanded if UDF does not return an error. **Usage:** ```go aMap := Map(map[string]interface{}{ "key1": 1, "key2": map[string]interface{}{ "subKey1":10, "subKey2":20, }, "key3": "subKey2", "array": []interface{}{ 111, 222, 333, }, "slice": []interface{}{ map[string]interface{}{ "attr1":111, "attr2":222, }, }, }) expandedText := aMap.ExpandAsText(`1: $key1, 2: ${array[2]} 3: $key2.subKey1 4: $key2[$key3] ${slice[0].attr1} 5: ${(key1 + 1) * 3} 6: $abc 7: end `) /* expands to 1: 1, 2: 333 3: 10 4: 20 111 5: 6 6: $abc 7: end */ ``` ## Expand arbitrary data structure ```go aMap := Map(map[string]interface{}{ "key1": 1, "key2": map[string]interface{}{ "subKey1":10, "subKey2":20, }, "key3": "subKey2", "array": []interface{}{ 111, 222, 333, }, "slice": []interface{}{ map[string]interface{}{ "attr1":111, "attr2":222, }, }, }) data := map[string]interface{}{ "k1": "$key1", "k2":"$array", "k3":"$key2", } expanded := aMap.Expand(data) /* expands to map[string]interface{}{ "k1": 1, "k2": []interface{}{111, 222, 333}, "k3": map[string]interface{}{ "subKey1":10, "subKey2":20, }, } */ ``` # UDF expandable User defined function You can add dynamic data substitution by registering function in top level map. ```go type Udf func(interface{}, Map) (interface{}, error) ``` ```go aMap: data.Map(map[string]interface{}{ "dailyCap": 100, "overallCap": 2, "AsFloat": func(source interface{}, state Map) (interface{}, error) { return toolbox.AsFloat(source), nil }, }) expanded := aMap.Expand("$AsFloat($dailyCap)") //expands to actual float: 100.0 ``` [Predefined UDF](udf) ### Compacted slice Using a generic data structure in a form []map[string]interface{} is extremely memory inefficient, CompactedSlice addresses the memory inefficiency by storing the new item values in a slice and by mapping corresponding fields to the item slice index positions. On top of that any neighboring nil values can be compacted too. **Usage** ```go collection := NewCompactedSlice(true, true) for i := 0;i<10;i++ { collection.Add(map[string]interface{}{ "f1": i+1, "f12": i+10, "f15": i*20, "f20": i+4, "f11": nil, "f12": nil, "f13": nil, "f14": "", }) } collection.Range(func(data interface{}) (bool, error) { actual = append(actual, toolbox.AsMap(data)) return true, nil }) ```toolbox-0.33.2/data/collection.go000066400000000000000000000047051374110251100166610ustar00rootroot00000000000000package data import ( "github.com/viant/toolbox" "strings" ) //Collection represents a slice of interface{} (generic type) type Collection []interface{} //Push appends provided value to the slice func (s *Collection) Push(value interface{}) { (*s) = append(*s, value) } //PadWithMap creates missing elements with a map func (s *Collection) PadWithMap(size int) { for i := len(*s); i < size; i++ { s.Push(NewMap()) } } //Range iterates over every item in this collection as long as handler returns true. Handler takes an index and index of the slice element. func (s *Collection) Range(handler func(item interface{}, index int) (bool, error)) error { for i, elem := range *s { next, err := handler(elem, i) if err != nil { return err } if !next { break } } return nil } //RangeMap iterates every map item in this collection as long as handler returns true. Handler takes an index and index of the slice element func (s *Collection) RangeMap(handler func(item Map, index int) (bool, error)) error { var next bool var err error for i, elem := range *s { var aMap, ok = elem.(Map) if !ok { next, err = handler(nil, i) } else { next, err = handler(aMap, i) } if err != nil { return err } if !next { break } } return nil } //RangeMap iterates every string item in this collection as long as handler returns true. Handler takes an index and index of the slice element func (s *Collection) RangeString(handler func(item interface{}, index int) (bool, error)) error { for i, elem := range *s { next, err := handler(toolbox.AsString(elem), i) if err != nil { return err } if !next { break } } return nil } //RangeMap iterates every int item in this collection as long as handler returns true. Handler takes an index and index of the slice element func (s *Collection) RangeInt(handler func(item interface{}, index int) (bool, error)) error { for i, elem := range *s { next, err := handler(toolbox.AsInt(elem), i) if err != nil { return err } if !next { break } } return nil } //String returns a string representation of this collection func (s *Collection) String() string { var items = make([]string, 0) for _, item := range *s { items = append(items, toolbox.AsString(item)) } return "[" + strings.Join(items, ",") + "]" } //NewCollection creates a new collection and returns a pointer func NewCollection() *Collection { var result Collection = make([]interface{}, 0) return &result } toolbox-0.33.2/data/compacted.go000066400000000000000000000214211374110251100164570ustar00rootroot00000000000000package data import ( "bytes" "encoding/json" "fmt" "github.com/viant/toolbox" "reflect" "sync" "sync/atomic" ) type Field struct { Name string Type reflect.Type index int } type nilGroup int //CompactedSlice represented a compacted slice to represent object collection type CompactedSlice struct { omitEmpty bool compressNils bool lock *sync.RWMutex fieldNames map[string]*Field fields []*Field data [][]interface{} size int64 RawEncoding bool } func (d CompactedSlice) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) _, err := buf.Write([]byte("[")) if err != nil { return nil, err } i := 0 if err = d.Range(func(item interface{}) (b bool, err error) { if i > 0 { _, err := buf.Write([]byte(",")) if err != nil { return false, err } } i++ data, err :=json.Marshal(item) if err != nil { return false, err } _, err = buf.Write(data) return err == nil, err });err != nil { return nil, err } if _, err := buf.Write([]byte("]")); err != nil { return nil, err } return buf.Bytes(), nil } func (s *CompactedSlice) Fields() []*Field { return s.fields } //Size returns size of collection func (s *CompactedSlice) Size() int { return int(atomic.LoadInt64(&s.size)) } func (s *CompactedSlice) index(fieldName string, value interface{}) int { s.lock.RLock() f, ok := s.fieldNames[fieldName] s.lock.RUnlock() if ok { return f.index } f = &Field{Name: fieldName, index: len(s.fieldNames), Type: reflect.TypeOf(value)} s.lock.Lock() defer s.lock.Unlock() s.fieldNames[fieldName] = f s.fields = append(s.fields, f) return f.index } func expandIfNeeded(size int, data []interface{}) []interface{} { if size >= len(data) { for i := len(data); i < size; i++ { data = append(data, nil) } } return data } func (s *CompactedSlice) compress(data []interface{}) []interface{} { var compressed = make([]interface{}, 0) var nilCount = 0 for _, item := range data { if item != nil { switch nilCount { case 0: case 1: compressed = append(compressed, nil) default: compressed = append(compressed, nilGroup(nilCount)) } compressed = append(compressed, item) nilCount = 0 continue } nilCount++ } return compressed } func (s *CompactedSlice) uncompress(in, out []interface{}) { var index = 0 for i := 0; i < len(in); i++ { var item = in[i] nilGroup, ok := item.(nilGroup) if !ok { out[index] = item index++ continue } for j := 0; j < int(nilGroup); j++ { out[index] = nil index++ } } for i := index; i < len(out); i++ { out[i] = nil } } //Add adds data to a collection func (s *CompactedSlice) Add(data map[string]interface{}) { var initSize = len(s.fieldNames) if initSize < len(data) { initSize = len(data) } atomic.AddInt64(&s.size, 1) var record = make([]interface{}, initSize) for k, v := range data { i := s.index(k, v) if !(i < len(record)) { record = expandIfNeeded(i+1, record) } if s.omitEmpty { if toolbox.IsString(v) { if toolbox.AsString(v) == "" { v = nil } } else if toolbox.IsInt(v) { if toolbox.AsInt(v) == 0 { v = nil } } else if toolbox.IsFloat(v) { if toolbox.AsFloat(v) == 0.0 { v = nil } } } record[i] = v } if s.compressNils { record = s.compress(record) } s.data = append(s.data, record) } func (s *CompactedSlice) mapNamesToFieldPositions(names []string) ([]int, error) { var result = make([]int, 0) for _, name := range names { field, ok := s.fieldNames[name] if !ok { return nil, fmt.Errorf("failed to lookup Field: %v", name) } result = append(result, field.index) } return result, nil } //SortedRange sort collection by supplied index and then call for each item supplied handler callback func (s *CompactedSlice) SortedRange(indexBy []string, handler func(item interface{}) (bool, error)) error { s.lock.Lock() fields := s.fields data := s.data s.data = [][]interface{}{} s.lock.Unlock() indexByPositions, err := s.mapNamesToFieldPositions(indexBy) if err != nil { return err } var indexedRecords = make(map[interface{}][]interface{}) var record = make([]interface{}, len(s.fields)) var key interface{} for _, item := range data { atomic.AddInt64(&s.size, -1) if s.compressNils { s.uncompress(item, record) } else { record = item } key = indexValue(indexByPositions, item) indexedRecords[key] = item } keys, err := sortKeys(key, indexedRecords) if err != nil { return err } for _, key := range keys { item := indexedRecords[key] if s.compressNils { s.uncompress(item, record) } else { record = item } var aMap = map[string]interface{}{} recordToMap(fields, record, aMap) if next, err := handler(aMap); !next || err != nil { return err } } return nil } //SortedIterator returns sorted iterator func (s *CompactedSlice) SortedIterator(indexBy []string) (toolbox.Iterator, error) { s.lock.Lock() fields := s.fields data := s.data s.data = [][]interface{}{} s.lock.Unlock() if len(indexBy) == 0 { return nil, fmt.Errorf("indexBy was empty") } indexByPositions, err := s.mapNamesToFieldPositions(indexBy) if err != nil { return nil, err } var record = make([]interface{}, len(fields)) var indexedRecords = make(map[interface{}][]interface{}) var key interface{} for _, item := range data { atomic.AddInt64(&s.size, -1) if s.compressNils { s.uncompress(item, record) } else { record = item } key = indexValue(indexByPositions, record) indexedRecords[key] = item } data = nil keys, err := sortKeys(key, indexedRecords) if err != nil { return nil, err } atomic.AddInt64(&s.size, int64(-len(data))) return &iterator{ size: len(indexedRecords), provider: func(index int) (map[string]interface{}, error) { if index >= len(indexedRecords) { return nil, fmt.Errorf("index: %d out bounds:%d", index, len(data)) } key := keys[index] item := indexedRecords[key] if s.compressNils { s.uncompress(item, record) } else { record = item } var aMap = map[string]interface{}{} recordToMap(fields, record, aMap) return aMap, nil }, }, nil } //Range iterate over slice, and remove processed data from the compacted slice func (s *CompactedSlice) Range(handler func(item interface{}) (bool, error)) error { s.lock.Lock() fields := s.fields data := s.data s.data = [][]interface{}{} s.lock.Unlock() var record = make([]interface{}, len(s.fields)) for _, item := range data { atomic.AddInt64(&s.size, -1) if s.compressNils { s.uncompress(item, record) } else { record = item } var aMap = map[string]interface{}{} recordToMap(fields, record, aMap) if next, err := handler(aMap); !next || err != nil { return err } } return nil } //Ranger moves data from slice to ranger func (s *CompactedSlice) Ranger() toolbox.Ranger { s.lock.Lock() clone := &CompactedSlice{ data: s.data, fields: s.fields, size: s.size, omitEmpty: s.omitEmpty, compressNils: s.compressNils, lock: &sync.RWMutex{}, fieldNames: s.fieldNames, } s.data = [][]interface{}{} atomic.StoreInt64(&s.size, 0) s.lock.Unlock() return clone } //Iterator returns a slice iterator func (s *CompactedSlice) Iterator() toolbox.Iterator { s.lock.Lock() fields := s.fields data := s.data s.data = [][]interface{}{} s.lock.Unlock() atomic.AddInt64(&s.size, int64(-len(data))) var record = make([]interface{}, len(fields)) return &iterator{ size: len(data), provider: func(index int) (map[string]interface{}, error) { if index >= len(data) { return nil, fmt.Errorf("index: %d out bounds:%d", index, len(data)) } item := data[index] if s.compressNils { s.uncompress(item, record) } else { record = item } var aMap = map[string]interface{}{} recordToMap(fields, record, aMap) return aMap, nil }, } } type iterator struct { size int provider func(index int) (map[string]interface{}, error) index int } //HasNext returns true if iterator has next element. func (i *iterator) HasNext() bool { return i.index < i.size } //Next sets item pointer with next element. func (i *iterator) Next(itemPointer interface{}) error { record, err := i.provider(i.index) if err != nil { return err } switch pointer := itemPointer.(type) { case *map[string]interface{}: *pointer = record case *interface{}: *pointer = record default: return fmt.Errorf("unsupported type: %T, expected *map[string]interface{}", itemPointer) } i.index++ return nil } //NewCompactedSlice create new compacted slice func NewCompactedSlice(omitEmpty, compressNils bool) *CompactedSlice { return &CompactedSlice{ omitEmpty: omitEmpty, compressNils: compressNils, fields: make([]*Field, 0), fieldNames: make(map[string]*Field), data: make([][]interface{}, 0), lock: &sync.RWMutex{}, } } toolbox-0.33.2/data/compacted_test.go000066400000000000000000000231331374110251100175200ustar00rootroot00000000000000package data import ( "encoding/json" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" "time" ) func TestNewCollection(t *testing.T) { if ! canRun64BitArch() { t.Skip() } collection := NewCompactedSlice(true, true) collection.Add(map[string]interface{}{ "f1": 1, "f12": 1, "f15": 1, "f20": 1, "f11": nil, "f13": "", }) collection.Add(map[string]interface{}{ "f1": 1, "f32": 1, "f35": 1, "f30": 1, "f31": nil, "f33": "", "f11": 0, "f36": 0.0, }) var actual = []map[string]interface{}{} err := collection.Range(func(data interface{}) (bool, error) { actual = append(actual, toolbox.AsMap(data)) return true, nil }) assert.Nil(t, err) assert.Equal(t, 2, len(actual)) assert.Equal(t, map[string]interface{}{ "f1": 1, "f12": 1, "f15": 1, "f20": 1, }, actual[0]) assert.Equal(t, map[string]interface{}{ "f1": 1, "f32": 1, "f35": 1, "f30": 1, }, actual[1]) } func Test_optimizedStorage(t *testing.T) { if ! canRun64BitArch() { t.Skip() } collection := NewCompactedSlice(true, true) var data = []interface{}{nil, nil, nil, "123", nil, nil, "abc", 12, nil, nil, nil, "a"} var compressed = []interface{}{nilGroup(3), "123", nilGroup(2), "abc", 12, nilGroup(3), "a"} var optimized = collection.compress(data) assert.EqualValues(t, compressed, optimized) collection.fields = make([]*Field, 12) var uncompressed = make([]interface{}, len(collection.fields)) collection.uncompress(compressed, uncompressed) assert.EqualValues(t, data, uncompressed) } func TestCompactedSlice_SortedRange(t *testing.T) { if ! canRun64BitArch() { t.Skip() } var useCases = []struct { description string data []map[string]interface{} expected []interface{} indexBy []string hasError bool }{ { description: "int sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10, "name": "name 10", }, { "id": 3, "name": "name 3", }, { "id": 1, "name": "name 1", }, { "id": 2, "name": "name 2", }, }, expected: []interface{}{ 1, 2, 3, 10, }, }, { description: "float sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10.0, "name": "name 10", }, { "id": 3.1, "name": "name 3", }, { "id": 1.2, "name": "name 1", }, { "id": 2.2, "name": "name 2", }, }, expected: []interface{}{ 1.2, 2.2, 3.1, 10.0, }, }, { description: "string sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": "010", "name": "name 10", }, { "id": "003", "name": "name 3", }, { "id": "001", "name": "name 1", }, { "id": "022", "name": "name 2", }, }, expected: []interface{}{ "001", "003", "010", "022", }, }, { description: "combined index sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 1, "u": 1, "name": "name 10", }, { "id": 3, "u": 2, "name": "name 3", }, { "id": 2, "u": 2, "name": "name 1", }, { "id": 4, "u": 6, "name": "name 2", }, }, expected: []interface{}{ 1, 2, 3, 4, }, }, { description: "missing Field", indexBy: []string{"field1"}, data: []map[string]interface{}{ { "id": 1, "u": 1, "name": "name 10", }, }, hasError: true, }, { description: "unsupported index type Field", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": time.Now(), "u": 1, "name": "name 10", }, }, hasError: true, }, } for _, useCase := range useCases { collection := NewCompactedSlice(true, true) var actual = make([]interface{}, 0) for _, item := range useCase.data { collection.Add(item) } err := collection.SortedRange(useCase.indexBy, func(item interface{}) (b bool, e error) { record := toolbox.AsMap(item) actual = append(actual, record[useCase.indexBy[0]]) return true, nil }) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } if !assert.Nil(t, err, useCase.description) { continue } assert.EqualValues(t, useCase.expected, actual, useCase.description) } } func TestCompactedSlice_SortedIterator(t *testing.T) { if ! canRun64BitArch() { t.Skip() } var useCases = []struct { description string data []map[string]interface{} expected []interface{} indexBy []string hasError bool }{ { description: "int sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10, "name": "name 10", }, { "id": 3, "name": "name 3", }, { "id": 1, "name": "name 1", }, { "id": 2, "name": "name 2", }, }, expected: []interface{}{ 1, 2, 3, 10, }, }, { description: "float sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10.0, "name": "name 10", }, { "id": 3.1, "name": "name 3", }, { "id": 1.2, "name": "name 1", }, { "id": 2.2, "name": "name 2", }, }, expected: []interface{}{ 1.2, 2.2, 3.1, 10.0, }, }, { description: "string sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": "010", "name": "name 10", }, { "id": "003", "name": "name 3", }, { "id": "001", "name": "name 1", }, { "id": "022", "name": "name 2", }, }, expected: []interface{}{ "001", "003", "010", "022", }, }, { description: "combined index sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 1, "u": 1, "name": "name 10", }, { "id": 3, "u": 2, "name": "name 3", }, { "id": 2, "u": 2, "name": "name 1", }, { "id": 4, "u": 6, "name": "name 2", }, }, expected: []interface{}{ 1, 2, 3, 4, }, }, { description: "missing Field", indexBy: []string{"field1"}, data: []map[string]interface{}{ { "id": 1, "u": 1, "name": "name 10", }, }, hasError: true, }, { description: "unsupported index type Field", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": time.Now(), "u": 1, "name": "name 10", }, }, hasError: true, }, } for _, useCase := range useCases { collection := NewCompactedSlice(true, true) var actual = make([]interface{}, 0) for _, item := range useCase.data { collection.Add(item) } iterator, err := collection.SortedIterator(useCase.indexBy) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } if !assert.Nil(t, err, useCase.description) { continue } var record map[string]interface{} for iterator.HasNext() { err = iterator.Next(&record) assert.Nil(t, err) actual = append(actual, record[useCase.indexBy[0]]) } assert.EqualValues(t, useCase.expected, actual, useCase.description) } } func TestCompactedSlice_Iterator(t *testing.T) { if ! canRun64BitArch() { t.Skip() } var useCases = []struct { description string data []map[string]interface{} expected []interface{} indexBy []string hasError bool }{ { description: "int sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10, "name": "name 10", }, { "id": 3, "name": "name 3", }, { "id": 1, "name": "name 1", }, }, expected: []interface{}{ 10, 3, 1, }, }, { description: "float sorting", indexBy: []string{"id"}, data: []map[string]interface{}{ { "id": 10.0, "name": "name 10", }, { "id": 3.1, "name": "name 3", }, { "id": 2.2, "name": "name 2", }, }, expected: []interface{}{ 10.0, 3.1, 2.2, }, }, } for _, useCase := range useCases { collection := NewCompactedSlice(true, true) var actual = make([]interface{}, 0) for _, item := range useCase.data { collection.Add(item) } iterator := collection.Iterator() var record map[string]interface{} for iterator.HasNext() { err := iterator.Next(&record) assert.Nil(t, err) actual = append(actual, record[useCase.indexBy[0]]) } assert.EqualValues(t, useCase.expected, actual, useCase.description) } } func TestCompactedSlice_MarshalJSON(t *testing.T) { if ! canRun64BitArch() { t.Skip() } var useCases = []struct { description string data []map[string]interface{} hasError bool }{ { description: "array marshaling", data: []map[string]interface{}{ { "id": float64(10), "name": "name 10", }, { "id": float64(3), "name": "name 3", }, { "id": float64(1), "name": "name 1", }, }, }, } for _, useCase := range useCases { collection := NewCompactedSlice(true, true) for _, item := range useCase.data { collection.Add(item) } rawJSON, err := json.Marshal(collection) if ! assert.Nil(t, err, useCase.description) { continue } actual := []map[string]interface{}{} json.Unmarshal(rawJSON, &actual) assert.EqualValues(t, useCase.data, actual) } } func canRun64BitArch() bool { isNot64BitArch := 32 << uintptr(^uintptr(0)>>63) < 64 return ! isNot64BitArch }toolbox-0.33.2/data/helper.go000066400000000000000000000050071374110251100160010ustar00rootroot00000000000000package data import ( "fmt" "github.com/viant/toolbox" "reflect" "sort" "strings" "unicode" ) func ExtractPath(expression string) string { var result = "" for _, r := range expression { aChar := string(r) if unicode.IsLetter(r) || unicode.IsDigit(r) || aChar == "[" || aChar == "]" || aChar == "." || aChar == "_" || aChar == "{" || aChar == "}" { result += aChar } } return strings.Trim(result, "{}") } func recordToMap(fields []*Field, record []interface{}, aMap map[string]interface{}) { for _, field := range fields { index := field.index var value = record[index] if value == nil { continue } aMap[field.Name] = value } } func indexValue(indexBy []int, record []interface{}) interface{} { if len(indexBy) == 1 { return record[indexBy[0]] } var values = make([]string, len(indexBy)) for i, fieldIndex := range indexBy { values[i] = toolbox.AsString(record[fieldIndex]) } return strings.Join(values, "-") } func intsToGenericSlice(keyType reflect.Type, aSlice []int) []interface{} { var result = make([]interface{}, len(aSlice)) for i, item := range aSlice { result[i] = reflect.ValueOf(item).Convert(keyType).Interface() } return result } func floatsToGenericSlice(keyType reflect.Type, aSlice []float64) []interface{} { var result = make([]interface{}, len(aSlice)) for i, item := range aSlice { result[i] = reflect.ValueOf(item).Convert(keyType).Interface() } return result } func stringsToGenericSlice(aSlice []string) []interface{} { var result = make([]interface{}, len(aSlice)) for i, item := range aSlice { result[i] = toolbox.AsString(item) } return result } func sortKeys(key interface{}, aMap map[interface{}][]interface{}) ([]interface{}, error) { if len(aMap) == 0 { return []interface{}{}, nil } var i = 0 switch key.(type) { case int, uint, uint8, uint16, uint32, uint64, int8, int16, int32, int64: var aSlice = make([]int, len(aMap)) for k := range aMap { aSlice[i] = toolbox.AsInt(k) i++ } sort.Ints(aSlice) return intsToGenericSlice(reflect.TypeOf(key), aSlice), nil case float64, float32: var aSlice = make([]float64, len(aMap)) for k := range aMap { aSlice[i] = toolbox.AsFloat(k) i++ } sort.Float64s(aSlice) return floatsToGenericSlice(reflect.TypeOf(key), aSlice), nil case string: var aSlice = make([]string, len(aMap)) for k := range aMap { aSlice[i] = toolbox.AsString(k) i++ } sort.Strings(aSlice) return stringsToGenericSlice(aSlice), nil } return nil, fmt.Errorf("unable sort, unsupported type: %T", key) } toolbox-0.33.2/data/helper_test.go000066400000000000000000000005451374110251100170420ustar00rootroot00000000000000package data import ( "github.com/stretchr/testify/assert" "testing" ) func Test_ExtractPath(t *testing.T) { assert.EqualValues(t, "var", ExtractPath("++$var")) assert.EqualValues(t, "var.z", ExtractPath("->${var.z}")) assert.EqualValues(t, "var.key[0]", ExtractPath("<-$var.key[0]")) assert.EqualValues(t, "var.z", ExtractPath("->${var.z}")) } toolbox-0.33.2/data/map.go000066400000000000000000000460331374110251100153030ustar00rootroot00000000000000package data import ( "bytes" "github.com/viant/toolbox" "log" "strings" "time" ) //Map types is an alias type to map[string]interface{} with extended behaviours type Map map[string]interface{} //Udf represents a user defined function used to transform data. type Udf func(interface{}, Map) (interface{}, error) //Put puts key value into the map. func (s *Map) Put(key string, value interface{}) { (*s)[key] = value } //Delete removes the supplied keys, it supports key of path expression with dot i.e. request.method func (s *Map) Delete(keys ...string) { for _, key := range keys { if !strings.Contains(key, ".") { delete(*s, key) continue } keyParts := strings.Split(key, ".") var temp = *s for i, part := range keyParts { if temp == nil { break } isLasPart := i+1 == len(keyParts) if isLasPart { delete(temp, part) } else if temp[part] != nil && toolbox.IsMap(temp[part]) { subMap := toolbox.AsMap(temp[part]) temp = Map(subMap) } else { break } } } } //Replace replaces supplied key/path with corresponding value func (s *Map) Replace(key, val string) { if !strings.Contains(key, ".") { (*s)[key] = val return } keyParts := strings.Split(key, ".") var temp = *s for i, part := range keyParts { if temp == nil { break } isLasPart := i+1 == len(keyParts) if isLasPart { temp[part] = val } else if temp[part] != nil && toolbox.IsMap(temp[part]) { subMap := toolbox.AsMap(temp[part]) temp = Map(subMap) } else { break } } } //Has returns true if the provided key is present func (s *Map) Has(key string) bool { _, found := (*s)[key] return found } //Get returns a value for provided key func (s *Map) Get(key string) interface{} { if result, found := (*s)[key]; found { return result } return nil } /* GetValue returns value for provided expression. The expression uses dot (.) to denote nested data structure. The following expression as supported 1) <-key shift 2) ++key pre increment 3) key++ post increment 4) $key reference access */ func (s *Map) GetValue(expr string) (interface{}, bool) { if expr == "" { return nil, false } if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { expr = expr[1 : len(expr)-1] } isShiftOperation := strings.HasPrefix(expr, "<-") if isShiftOperation { expr = string(expr[2:]) } isPostIncrementOperation := strings.HasSuffix(expr, "++") if isPostIncrementOperation { expr = string(expr[:len(expr)-2]) } isPreIncrementOperation := strings.HasPrefix(expr, "++") if isPreIncrementOperation { expr = string(expr[2:]) } isReference := strings.HasPrefix(expr, "$") if isReference { expr = string(expr[1:]) expr = s.GetString(expr) if expr == "" { return nil, false } } state := *s if strings.Contains(expr, ".") || strings.HasSuffix(expr, "]") { fragments := strings.Split(expr, ".") for i, fragment := range fragments { var index interface{} arrayIndexPosition := strings.Index(fragment, "[") if arrayIndexPosition != -1 { arrayEndPosition := strings.Index(fragment, "]") if arrayEndPosition > arrayIndexPosition && arrayEndPosition < len(fragment) { arrayIndex := string(fragment[arrayIndexPosition+1 : arrayEndPosition]) index = arrayIndex fragment = string(fragment[:arrayIndexPosition]) } } isLast := i+1 == len(fragments) hasKey := state.Has(fragment) if !hasKey { return nil, false } var candidate = state.Get(fragment) if !isLast && candidate == nil { return nil, false } if index != nil { if intIndex, err := toolbox.ToInt(index); err == nil { if !toolbox.IsSlice(candidate) { return nil, false } var aSlice = toolbox.AsSlice(candidate) if intIndex >= len(aSlice) { return nil, false } if intIndex < len(aSlice) { candidate = aSlice[intIndex] } else { candidate = nil } } else if textIndex, ok := index.(string); ok { if !toolbox.IsMap(candidate) { return nil, false } aMap := toolbox.AsMap(candidate) if candidate, ok = aMap[textIndex]; !ok { return nil, false } } else { return nil, false } if isLast { return candidate, true } } if isLast { expr = fragment continue } if toolbox.IsMap(candidate) { newState := toolbox.AsMap(candidate) if newState != nil { state = newState } } else { value, _ := state.GetValue(fragment) if f, ok := value.(func(key string) interface{}); ok { return f(fragments[i+1]), true } return nil, false } } } if state.Has(expr) { var result = state.Get(expr) if isPostIncrementOperation { state.Put(expr, toolbox.AsInt(result)+1) } else if isPreIncrementOperation { result = toolbox.AsInt(result) + 1 state.Put(expr, result) } else if isShiftOperation { aCollection := state.GetCollection(expr) if len(*aCollection) == 0 { return nil, false } var result = (*aCollection)[0] var newCollection = (*aCollection)[1:] state.Put(expr, &newCollection) return result, true } if f, ok := result.(func() interface{}); ok { return f(), true } return result, true } return nil, false } /* Set value sets value in the map for the supplied expression. The expression uses dot (.) to denote nested data structure. For instance the following expression key1.subKey1.attr1 Would take or create map for key1, followied bu takeing or creating antother map for subKey1 to set attr1 key with provided value The following expression as supported 1) $key reference - the final key is determined from key's content. 2) ->key push expression to append value at the end of the slice */ func (s *Map) SetValue(expr string, value interface{}) { if expr == "" { return } if value == nil { return } if strings.Index(expr, "$") != -1 { expr = s.ExpandAsText(expr) } state := *s isPushOperation := strings.HasPrefix(expr, "->") if isPushOperation { expr = string(expr[2:]) } if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { expr = expr[1 : len(expr)-1] } if strings.Contains(expr, ".") { fragments := strings.Split(expr, ".") nodePath := strings.Join(fragments[:len(fragments)-1], ".") if node, ok := s.GetValue(nodePath); ok && toolbox.IsMap(node) { if _, writable := node.(map[string]interface{}); !writable { node = Map(toolbox.AsMap(node)) s.SetValue(nodePath, node) } expr = fragments[len(fragments)-1] state = Map(toolbox.AsMap(node)) } else { for i, fragment := range fragments { isLast := i+1 == len(fragments) if isLast { expr = fragment } else { subState := state.GetMap(fragment) if subState == nil { subState = NewMap() state.Put(fragment, subState) } state = subState } } } } if isPushOperation { collection := state.GetCollection(expr) if collection == nil { collection = NewCollection() state.Put(expr, collection) } collection.Push(value) state.Put(expr, collection) return } state.Put(expr, value) } //Apply copies all elements of provided map to this map. func (s *Map) Apply(source map[string]interface{}) { for k, v := range source { (*s)[k] = v } } //GetString returns value for provided key as string. func (s *Map) GetString(key string) string { if result, found := (*s)[key]; found { return toolbox.AsString(result) } return "" } //GetInt returns value for provided key as int. func (s *Map) GetInt(key string) int { if result, found := (*s)[key]; found { return toolbox.AsInt(result) } return 0 } //GetFloat returns value for provided key as float64. func (s *Map) GetFloat(key string) float64 { if result, found := (*s)[key]; found { return toolbox.AsFloat(result) } return 0.0 } //GetBoolean returns value for provided key as boolean. func (s *Map) GetBoolean(key string) bool { if result, found := (*s)[key]; found { return toolbox.AsBoolean(result) } return false } //GetCollection returns value for provided key as collection pointer. func (s *Map) GetCollection(key string) *Collection { if result, found := (*s)[key]; found { collectionPointer, ok := result.(*Collection) if ok { return collectionPointer } aSlice, ok := result.([]interface{}) collection := Collection(aSlice) if ok { return &collection } if !toolbox.IsSlice(result) { return nil } aSlice = toolbox.AsSlice(result) collection = Collection(aSlice) return &collection } return nil } //GetMap returns value for provided key as map. func (s *Map) GetMap(key string) Map { if result, found := (*s)[key]; found { aMap, ok := result.(Map) if ok { return aMap } aMap, ok = result.(map[string]interface{}) if ok { return aMap } var result = toolbox.AsMap(result) (*s)[key] = result return result } return nil } //Range iterates every key, value pair of this map, calling supplied callback as long it does return true. func (s *Map) Range(callback func(k string, v interface{}) (bool, error)) error { for k, v := range *s { next, err := callback(k, v) if err != nil { return err } if !next { break } } return nil } //Clones create a clone of this map. func (s *Map) Clone() Map { var result = NewMap() for key, value := range *s { if aMap, casted := value.(Map); casted { result[key] = aMap.Clone() } else { result[key] = value } } return result } //Returns a map that can be encoded by json or other decoders. //Since a map can store a function, it filters out any non marshalable types. func (s *Map) AsEncodableMap() map[string]interface{} { var result = make(map[string]interface{}) for k, v := range *s { if v == nil { continue } result[k] = asEncodableValue(v) } return result } //asEncodableValue returns all non func values or func() literal for function. func asEncodableValue(v interface{}) interface{} { if v == nil { return nil } value := v if toolbox.IsFunc(v) { return "func()" } if toolbox.IsMap(v) { var aMap = Map(toolbox.AsMap(v)) value = aMap.AsEncodableMap() } else if toolbox.IsSlice(v) { var targetSlice = make([]interface{}, 0) var sourceSlice = toolbox.AsSlice(v) for _, item := range sourceSlice { targetSlice = append(targetSlice, asEncodableValue(item)) } value = targetSlice } else if toolbox.IsString(v) || toolbox.IsInt(v) || toolbox.IsFloat(v) { value = v } else { value = toolbox.AsString(v) } return value } func hasGenericKeys(aMap map[string]interface{}) bool { for k := range aMap { if strings.HasPrefix(k, "$AsInt") || strings.HasPrefix(k, "$AsFloat") || strings.HasPrefix(k, "$AsBool") { return true } } return false } //Expand expands provided value of any type with dollar sign expression/ func (s *Map) Expand(source interface{}) interface{} { switch value := source.(type) { case bool, []byte, int, uint, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, time.Time: return source case *[]byte: return s.expandExpressions(string(*value)) case *string: if value == nil { return "" } return s.expandExpressions(*value) case string: return s.expandExpressions(value) case map[string]interface{}: if hasGenericKeys(value) { result := make(map[interface{}]interface{}) for k, v := range value { var key = s.Expand(k) result[key] = s.Expand(v) } return result } resultMap := make(map[string]interface{}) for k, v := range value { var key = s.ExpandAsText(k) var expanded = s.Expand(v) if key == "..." { if expanded != nil && toolbox.IsMap(expanded) { for key, value := range toolbox.AsMap(expanded) { resultMap[key] = value } continue } } resultMap[key] = expanded } return resultMap case map[interface{}]interface{}: var resultMap = make(map[interface{}]interface{}) for k, v := range value { var key = s.Expand(k) var expanded = s.Expand(v) if key == "..." { if expanded != nil && toolbox.IsMap(expanded) { for key, value := range toolbox.AsMap(expanded) { resultMap[key] = value } continue } } resultMap[key] = expanded } return resultMap case []interface{}: var resultSlice = make([]interface{}, len(value)) for i, value := range value { resultSlice[i] = s.Expand(value) } return resultSlice default: if source == nil { return nil } if toolbox.IsMap(source) { switch aMap := value.(type) { case map[string]interface{}: return s.Expand(aMap) case map[interface{}]interface{}: return s.Expand(aMap) default: return s.Expand(toolbox.AsMap(value)) } } else if toolbox.IsSlice(source) { return s.Expand(toolbox.AsSlice(value)) } else if toolbox.IsStruct(value) { aMap := toolbox.AsMap(value) return s.Expand(aMap) } else if value != nil { return s.Expand(toolbox.AsString(value)) } } return source } //ExpandAsText expands all matching expressions starting with dollar sign ($) func (s *Map) ExpandAsText(text string) string { result := s.expandExpressions(text) if toolbox.IsSlice(result) || toolbox.IsMap(result) { buf := new(bytes.Buffer) err := toolbox.NewJSONEncoderFactory().Create(buf).Encode(result) if err == nil { return buf.String() } } if text, ok := result.(string); ok || result == nil { return text } return toolbox.AsString(result) } func (s *Map) evaluateUDF(candidate interface{}, argument interface{}) (interface{}, bool) { var canExpandAll = true if toolbox.IsString(argument) { var expandable = strings.TrimSpace(toolbox.AsString(argument)) Parse(expandable, func(expression string, udf bool, argument interface{}) (interface{}, bool) { if _, has := s.GetValue(string(expression[1:])); !has { canExpandAll = false } return nil, false }) } if !canExpandAll { return nil, false } udf, ok := candidate.(func(interface{}, Map) (interface{}, error)) if !ok { return nil, false } expandedArgument := s.expandArgumentsExpressions(argument) if toolbox.IsString(expandedArgument) { expandedText := toolbox.AsString(expandedArgument) if toolbox.IsStructuredJSON(expandedText) { evaluated, err := toolbox.JSONToInterface(expandedText) if err != nil { return nil, false } expandedArgument = evaluated } } evaluated, err := udf(expandedArgument, *s) if err == nil { return evaluated, true } log.Printf("failed to evaluate %v, %v", candidate, err) return nil, false } func (s *Map) hasCycle(source interface{}, ownerVariable string) bool { switch value := source.(type) { case string: return strings.Contains(value, ownerVariable) case Map: for k, v := range value { if s.hasCycle(k, ownerVariable) || s.hasCycle(v, ownerVariable) { return true } } case map[string]interface{}: for k, v := range value { if s.hasCycle(k, ownerVariable) || s.hasCycle(v, ownerVariable) { return true } } case []interface{}: for _, v := range value { if s.hasCycle(v, ownerVariable) { return true } } case Collection: for _, v := range value { if s.hasCycle(v, ownerVariable) { return true } } } return false } //expandExpressions will check provided text with any expression starting with dollar sign ($) to substitute it with key in the map if it is present. //The result can be an expanded text or type of key referenced by the expression. func (s *Map) expandExpressions(text string) interface{} { if strings.Index(text, "$") == -1 { return text } var expandVariable = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { value, hasExpValue := s.GetValue(string(expression[1:])) if hasExpValue { if value != expression && s.hasCycle(value, expression) { log.Printf("detected data cycle on %v in value: %v", expression, value) return expression, true } if isUDF { if evaluated, ok := s.evaluateUDF(value, argument); ok { return evaluated, true } } else { if value != nil && (toolbox.IsMap(value) || toolbox.IsSlice(value)) { return s.Expand(value), true } if text, ok := value.(string); ok { return text, true } if value != nil { return toolbox.DereferenceValue(value), true } return value, true } } if isUDF { expandedArgument := s.expandArgumentsExpressions(argument) _, isByteArray := expandedArgument.([]byte) if !toolbox.IsMap(expandedArgument) && !toolbox.IsSlice(expandedArgument) || isByteArray { argument = toolbox.AsString(expandedArgument) } return expression + "(" + toolbox.AsString(argument) + ")", true } return expression, true } return Parse(text, expandVariable) } //expandExpressions will check provided text with any expression starting with dollar sign ($) to substitute it with key in the map if it is present. //The result can be an expanded text or type of key referenced by the expression. func (s *Map) expandArgumentsExpressions(argument interface{}) interface{} { if argument == nil || !toolbox.IsString(argument) { return argument } argumentLiteral, ok := argument.(string) if ok { if toolbox.IsStructuredJSON(argumentLiteral) { return s.expandExpressions(argumentLiteral) } } var expandVariable = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { value, hasExpValue := s.GetValue(string(expression[1:])) if hasExpValue { if value != expression && s.hasCycle(value, expression) { log.Printf("detected data cycle on %v in value: %v", expression, value) return expression, true } if isUDF { if evaluated, ok := s.evaluateUDF(value, argument); ok { return evaluated, true } } else { if value != nil && (toolbox.IsMap(value) || toolbox.IsSlice(value)) { return s.Expand(value), true } if text, ok := value.(string); ok { return text, true } if value != nil { return toolbox.DereferenceValue(value), true } return value, true } } if isUDF { expandedArgument := s.expandArgumentsExpressions(argument) _, isByteArray := expandedArgument.([]byte) if !toolbox.IsMap(expandedArgument) && !toolbox.IsSlice(expandedArgument) || isByteArray { argument = toolbox.AsString(expandedArgument) } expression = expression + "(" + toolbox.AsString(argument) + ")" return expression, true } return expression, true } tokenizer := toolbox.NewTokenizer(argumentLiteral, invalidToken, eofToken, matchers) var result = make([]interface{}, 0) for tokenizer.Index < len(argumentLiteral) { match, err := toolbox.ExpectTokenOptionallyFollowedBy(tokenizer, whitespace, "expected argument", doubleQuoteEnclosedToken, comaToken, unmatchedToken, eofToken) if err != nil { return Parse(argumentLiteral, expandVariable) } switch match.Token { case doubleQuoteEnclosedToken: result = append(result, strings.Trim(match.Matched, `"`)) case comaToken: result = append(result, match.Matched) tokenizer.Index++ case unmatchedToken: result = append(result, match.Matched) } } for i, arg := range result { textArg, ok := arg.(string) if !ok { continue } textArg = strings.Trim(textArg, "'") result[i] = Parse(textArg, expandVariable) } if len(result) == 1 { return result[0] } return result } //NewMap creates a new instance of a map. func NewMap() Map { return make(map[string]interface{}) } toolbox-0.33.2/data/map_test.go000066400000000000000000000213011374110251100163310ustar00rootroot00000000000000package data import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestMap_GetValue(t *testing.T) { aMap := NewMap() { subCollection := NewCollection() subCollection.Push("item1") subCollection.Push("item2") aMap.Put("cc", subCollection) subMap := NewMap() subMap.Put("k1", 1) subMap.Put("k2", 1) aMap.Put("cc", subCollection) aMap.Put("keys", subMap) { value, has := aMap.GetValue("cc[0]") assert.True(t, has) assert.Equal(t, "item1", value) } { value, has := aMap.GetValue("keys[k1]") assert.True(t, has) assert.Equal(t, 1, value) } { _, has := aMap.GetValue("keys[k10]") assert.False(t, has) } } { metaMap := make(map[string]int) metaMap["USER"] = 7 aMap.Put("meta", metaMap) value, ok := aMap.GetValue("meta.USER") assert.True(t, ok) if !assert.Equal(t, 7, value) { return } aMap.SetValue("meta.USER", toolbox.AsInt(value)+1) value, ok = aMap.GetValue("meta.USER") assert.True(t, ok) if !assert.Equal(t, 8, value) { return } } { var collection = NewCollection() collection.Push("1") collection.Push("20") collection.Push("30") aMap.Put("collection", collection) var subMap = NewMap() subMap.Put("i", 10) subMap.Put("col", collection) aMap.Put("a", subMap) aMap.Put("b", "123") aMap.Put("c", "b") } { //test simple get operation value, has := aMap.GetValue("c") assert.True(t, has) assert.Equal(t, "b", value) } { //test get operation value, has := aMap.GetValue("a.col") assert.True(t, has) assert.Equal(t, []interface{}{"1", "20", "30"}, toolbox.AsSlice(value)) } { //test reference get operation value, has := aMap.GetValue("$c") assert.True(t, has) assert.Equal(t, "123", value) } { //test post increment operations value, has := aMap.GetValue("a.i++") assert.True(t, has) assert.Equal(t, 10, value) value, has = aMap.GetValue("a.i++") assert.True(t, has) assert.Equal(t, 11, value) } { //test pre increment operations value, has := aMap.GetValue("++a.i") assert.True(t, has) assert.Equal(t, 13, value) value, has = aMap.GetValue("++a.i") assert.True(t, has) assert.Equal(t, 14, value) } { // test shift value, has := aMap.GetValue("<-collection") assert.True(t, has) assert.Equal(t, "1", value) value, has = aMap.GetValue("<-collection") assert.True(t, has) assert.Equal(t, "20", value) } { // test array index var aCollection = NewCollection() aCollection.Push(map[string]interface{}{ "k1": 1, "K2": 2, }) aCollection.Push(map[string]interface{}{ "k2": 3, "K3": 4, }) aMap.Put("c", aCollection) value, has := aMap.GetValue("c[0].k1") assert.True(t, has) assert.Equal(t, 1, value) value, has = aMap.GetValue("c[1].k2") assert.True(t, has) assert.Equal(t, 3, value) } { subMap := NewMap() subCollection := NewCollection() subCollection.Push("item1") subCollection.Push("item2") subMap.Put("c", subCollection) aMap.Put("s", subMap) value, has := aMap.GetValue("s.c[0]") assert.True(t, has) assert.Equal(t, "item1", value) } } func TestMap_SetValue(t *testing.T) { aMap := NewMap() { // test simple Set _, has := aMap.GetValue("z.a") assert.False(t, has) aMap.SetValue("z.a", "123") value, has := aMap.GetValue("z.a") assert.True(t, has) assert.Equal(t, "123", value) } { // test reference set aMap.SetValue("z.b", "111") value, has := aMap.GetValue("z.b") assert.True(t, has) assert.Equal(t, "111", value) aMap.SetValue("zzz", "z.b") aMap.SetValue("$zzz", "222") value, has = aMap.GetValue("z.b") assert.True(t, has) assert.Equal(t, "222", value) } { //test push aMap.SetValue("->a.v", 1) aMap.SetValue("->a.v", 2) aCollection, has := aMap.GetValue("a.v") assert.True(t, has) assert.Equal(t, []interface{}{1, 2}, toolbox.AsSlice(aCollection)) } { //test mutated nested array collection := NewCollection() item := map[string]interface{}{ "key": 1, "attr": 2, } collection.Push(item) aMap.Put("col", collection) aMap.SetValue("col[0].x", 20) aMap.SetValue("col[0].attr", 30) assert.EqualValues(t, map[string]interface{}{ "key": 1, "attr": 30, "x": 20, }, item) } } func Test_Expand(t *testing.T) { state := NewMap() state.Put("name", "etly") build := NewMap() state.Put("build", build) build.Put("Target", "app") build.Put("Args", "-Dmvn.test.skip") var text = state.ExpandAsText("a $vv-ee /usr/local/app_${name}v1 $build.Target $abc $build.Args") assert.Equal(t, "a $vv-ee /usr/local/app_etlyv1 app $abc -Dmvn.test.skip", text) state.Put("nestedappleone", "juice") state.Put("tag", "apple") var txt2 = state.ExpandAsText("${nested${tag}one}") assert.Equal(t, "juice", txt2) text = "docker build -t $registryUsername/site_profile_backup:0.1.4 /site_profile_backup:0.1.4 /tmp/site_profile_backup/release/" state = NewMap() state.Put("registryUsername", "$registryUsername") expanded := state.Expand(text) assert.Equal(t, text, expanded) } func Test_ExpandCycleIssue(t *testing.T) { state := NewMap() originMap := NewMap() originMap.Put("URL", "$origin") state.Put("origin", originMap) var text = state.Expand("abc ${origin}\n ") assert.Equal(t, "abc {\"URL\":\"$origin\"}\n\n ", text) } func Test_ExpandFun(t *testing.T) { state := NewMap() state.Put("name", "etly") build := NewMap() state.Put("build", build) build.Put("Target", "app") build.Put("Args", "-Dmvn.test.skip") var text = state.ExpandAsText("a $vv-ee /usr/local/app_${name}v1 $build.Target $abc $build.Args") assert.Equal(t, "a $vv-ee /usr/local/app_etlyv1 app $abc -Dmvn.test.skip", text) } func Test_Udf(t *testing.T) { var test = func(s interface{}, m Map) (interface{}, error) { return fmt.Sprintf("%v", s), nil } var dateOfBirth = func(source interface{}, m Map) (interface{}, error) { if !toolbox.IsSlice(source) { return nil, fmt.Errorf("expected slice but had: %T %v", source, source) } return toolbox.NewDateOfBirthrovider().Get(toolbox.NewContext(), toolbox.AsSlice(source)...) } state := NewMap() state.Put("test", test) state.Put("name", "endly") state.Put("a", "1") state.Put("b", "2") state.Put("Dob", dateOfBirth) { var text = "$xyz($name)" expanded := state.Expand(text) assert.EqualValues(t, "$xyz(endly)", expanded) } { var text = "$xyz(hello $name $abc)" expanded := state.Expand(text) assert.EqualValues(t, "$xyz(hello endly $abc)", expanded) } { var text = "$test(hello $abc)" expanded := state.Expand(text) assert.EqualValues(t, "$test(hello $abc)", expanded) } { var text = "$test(hello $name $abc)" expanded := state.Expand(text) assert.EqualValues(t, "$test(hello endly $abc)", expanded) } { var text = "$test(hello $name)" expanded := state.Expand(text) assert.EqualValues(t, "hello endly", expanded) } { var text = "zz $a ${b}a" expanded := state.Expand(text) assert.EqualValues(t, "zz 1 2a", expanded) } } func Test_Delete(t *testing.T) { var state = NewMap() state.SetValue("k1.v1", 1) state.SetValue("k1.v2", 1) state.Put("k2", 1) state.Delete("k1.v1", "k2") assert.EqualValues(t, 1, len(state)) assert.EqualValues(t, 1, len(state.GetMap("k1"))) } func Test_Replace(t *testing.T) { var state = NewMap() state.SetValue("k1.v1", 1) state.SetValue("k1.v2", 1) state.Put("k2", 1) state.Replace("k1.v1", "v100") state.Replace("k2", "v200") assert.EqualValues(t, "v100", state.Expand("$k1.v1")) assert.EqualValues(t, "v200", state.Get("k2")) } func Test_ExpandAsText(t *testing.T) { aMap := Map(map[string]interface{}{ "key1": 1, "key2": map[string]interface{}{ "subKey1": 10, "subKey2": 20, }, "key3": "subKey2", "array": []interface{}{ 111, 222, 333, }, "slice": []interface{}{ map[string]interface{}{ "attr1": 111, "attr2": 222, }, }, }) expandedText := aMap.ExpandAsText(`1: $key1, 2: ${array[2]} 3: $key2.subKey1 4: $key2[$key3] ${slice[0].attr1} 5: ${(key1 + 1) * 3} `) assert.Equal(t, `1: 1, 2: 333 3: 10 4: 20 111 5: 6 `, expandedText) } func Test_SubState(t *testing.T) { state := NewMap() state.Put("meta", map[string]int{ "TABLE": 1, }) value, ok := state.GetValue("meta.TABLE") if !assert.True(t, ok) { return } state.SetValue("meta.TABLE", toolbox.AsInt(value)+1) value, ok = state.GetValue("meta.TABLE") if !assert.True(t, ok) { return } assert.EqualValues(t, 2, value) payload := []uint8{34, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 34} aMap := Map(map[string]interface{}{ "Payload": &payload, "AsString": func(source interface{}, state Map) (interface{}, error) { return toolbox.AsString(source), nil }}) expanded := aMap.Expand("$AsString($Payload)") assert.EqualValues(t, `"Hello World"`, expanded) } toolbox-0.33.2/data/parser.go000066400000000000000000000214671374110251100160260ustar00rootroot00000000000000package data import ( "bytes" "github.com/viant/toolbox" "math" "strings" ) const ( eofToken = -1 invalidToken = iota beforeVarToken varToken incToken decrementToken shiftToken enclosedVarToken callToken idToken arrayIndexToken unmatchedToken keyIndexToken whitespace groupingToken operatorTojeb doubleQuoteEnclosedToken comaToken ) var matchers = map[int]toolbox.Matcher{ beforeVarToken: toolbox.NewTerminatorMatcher("$"), varToken: toolbox.NewCharactersMatcher("$"), comaToken: toolbox.NewTerminatorMatcher(","), idToken: toolbox.NewCustomIdMatcher("_"), incToken: toolbox.NewKeywordsMatcher(true, "++"), decrementToken: toolbox.NewKeywordsMatcher(true, "--"), shiftToken: toolbox.NewKeywordsMatcher(true, "<-"), arrayIndexToken: toolbox.NewBodyMatcher("[", "]"), callToken: toolbox.NewBodyMatcher("(", ")"), enclosedVarToken: toolbox.NewBodyMatcher("{", "}"), doubleQuoteEnclosedToken: toolbox.NewBodyMatcher(`"`, `"`), keyIndexToken: toolbox.NewCustomIdMatcher("."), unmatchedToken: toolbox.NewRemainingSequenceMatcher(), groupingToken: toolbox.NewBodyMatcher("(", ")"), operatorTojeb: toolbox.NewTerminatorMatcher("+", "-", "*", "/", "^", "%"), whitespace: toolbox.NewCharactersMatcher(" \t\n\r"), } //Parse parses expression func Parse(expression string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { tokenizer := toolbox.NewTokenizer(expression, invalidToken, eofToken, matchers) var value interface{} var result = fragments{} var ok bool done := false for tokenizer.Index < len(expression) && !done { match := tokenizer.Nexts(beforeVarToken, varToken, unmatchedToken, eofToken) switch match.Token { case unmatchedToken: result.Append(match.Matched) done = true continue case eofToken: break case beforeVarToken: result.Append(match.Matched) continue case varToken: variable := "$" match = tokenizer.Nexts(idToken, enclosedVarToken, incToken, decrementToken, shiftToken) switch match.Token { case eofToken: result.Append(variable) continue case enclosedVarToken: expanded := expandEnclosed(match.Matched, handler) if toolbox.IsFloat(expanded) || toolbox.IsInt(expanded) { value = expanded result.Append(value) continue } expandedText := toolbox.AsString(expanded) if strings.HasSuffix(expandedText, ")") { value = Parse("$"+expandedText, handler) if textValue, ok := value.(string); ok && textValue == "$"+expandedText { value = "${" + expandedText + "}" } result.Append(value) continue } variable := "${" + expandedText + "}" if value, ok = handler(variable, false, ""); !ok { value = variable } result.Append(value) continue case incToken, decrementToken, shiftToken: variable += match.Matched match = tokenizer.Nexts(idToken) //enclosedVarToken, idToken ? if match.Token != idToken { result.Append(variable) continue } fallthrough case idToken: variable += match.Matched variable = expandVariable(tokenizer, variable, handler) match = tokenizer.Nexts(callToken, incToken, decrementToken, beforeVarToken, unmatchedToken, eofToken) switch match.Token { case callToken: arguments := string(match.Matched[1 : len(match.Matched)-1]) if value, ok = handler(variable, true, arguments); !ok { value = variable + match.Matched } result.Append(value) continue case incToken, decrementToken: variable += match.Matched match.Matched = "" fallthrough case beforeVarToken, unmatchedToken, eofToken, invalidToken: if value, ok = handler(variable, false, ""); !ok { value = variable } result.Append(value) result.Append(match.Matched) continue } default: result.Append(variable) } } } return result.Get() } func expandVariable(tokenizer *toolbox.Tokenizer, variable string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) string { match := tokenizer.Nexts(keyIndexToken, arrayIndexToken) switch match.Token { case keyIndexToken: variable = expandSubKey(variable, match, tokenizer, handler) case arrayIndexToken: variable = expandIndex(variable, match, handler, tokenizer) } return variable } func expandIndex(variable string, match *toolbox.Token, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool), tokenizer *toolbox.Tokenizer) string { variable += toolbox.AsString(Parse(match.Matched, handler)) match = tokenizer.Nexts(arrayIndexToken, keyIndexToken) switch match.Token { case keyIndexToken: variable = expandSubKey(variable, match, tokenizer, handler) case arrayIndexToken: variable += toolbox.AsString(Parse(match.Matched, handler)) } return variable } func expandSubKey(variable string, match *toolbox.Token, tokenizer *toolbox.Tokenizer, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) string { variable += match.Matched match = tokenizer.Nexts(idToken, enclosedVarToken, arrayIndexToken) switch match.Token { case idToken: variable += match.Matched variable = expandVariable(tokenizer, variable, handler) case enclosedVarToken: expanded := expandEnclosed(match.Matched, handler) variable += toolbox.AsString(expanded) case arrayIndexToken: variable = expandIndex(variable, match, handler, tokenizer) } return variable } func expandEnclosed(expr string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { if strings.HasPrefix(expr, "{") && strings.HasSuffix(expr, "}") { expr = string(expr[1 : len(expr)-1]) } tokenizer := toolbox.NewTokenizer(expr, invalidToken, eofToken, matchers) match, err := toolbox.ExpectTokenOptionallyFollowedBy(tokenizer, whitespace, "expected operatorTojeb", groupingToken, operatorTojeb) if err != nil { return Parse(expr, handler) } switch match.Token { case groupingToken: groupExpr := string(match.Matched[1 : len(match.Matched)-1]) result := expandEnclosed(groupExpr, handler) if !(toolbox.IsInt(result) || toolbox.IsFloat(result)) { return Parse(expr, handler) } expandedGroup := toolbox.AsString(result) + string(expr[tokenizer.Index:]) return expandEnclosed(expandedGroup, handler) case operatorTojeb: leftOperand, leftOk := tryNumericOperand(match.Matched, handler).(float64) operator := string(expr[tokenizer.Index : tokenizer.Index+1]) rightOperand, rightOk := tryNumericOperand(string(expr[tokenizer.Index+1:]), handler).(float64) if !leftOk || !rightOk { return Parse(expr, handler) } var floatResult float64 switch operator { case "+": floatResult = leftOperand + rightOperand case "-": floatResult = leftOperand - rightOperand case "/": if rightOperand == 0 { //division by zero issue return Parse(expr, handler) } floatResult = leftOperand / rightOperand case "*": floatResult = leftOperand * rightOperand case "^": floatResult = math.Pow(leftOperand, rightOperand) case "%": floatResult = float64(int(leftOperand) % int(rightOperand)) default: return Parse(expr, handler) } intResult := int(floatResult) if floatResult == float64(intResult) { return intResult } return floatResult } return Parse(expr, handler) } func tryNumericOperand(expression string, handler func(expression string, isUDF bool, argument interface{}) (interface{}, bool)) interface{} { expression = strings.TrimSpace(expression) if result, err := toolbox.ToFloat(expression); err == nil { return result } left := expandEnclosed(expression, handler) if result, err := toolbox.ToFloat(left); err == nil { return result } left = Parse("$"+expression, handler) if result, err := toolbox.ToFloat(left); err == nil { return result } return expression } func asExpandedText(source interface{}) string { if source != nil && (toolbox.IsSlice(source) || toolbox.IsMap(source)) { buf := new(bytes.Buffer) err := toolbox.NewJSONEncoderFactory().Create(buf).Encode(source) if err == nil { return buf.String() } } return toolbox.AsString(source) } type fragments []interface{} func (f *fragments) Append(item interface{}) { if text, ok := item.(string); ok { if text == "" { return } } *f = append(*f, item) } func (f fragments) Get() interface{} { count := len(f) if count == 0 { return "" } var emptyCount = 0 var result interface{} for _, item := range f { if text, ok := item.(string); ok && strings.TrimSpace(text) == "" { emptyCount++ } else { result = item } } if emptyCount == count-1 { return result } var textResult = "" for _, item := range f { textResult += asExpandedText(item) } return textResult } toolbox-0.33.2/data/parser_test.go000066400000000000000000000265411374110251100170630ustar00rootroot00000000000000package data import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "sort" "strings" "testing" ) func IndexOf(source interface{}, state Map) (interface{}, error) { if !toolbox.IsSlice(source) { return nil, fmt.Errorf("expected arguments but had: %T", source) } args := toolbox.AsSlice(source) if len(args) != 2 { return nil, fmt.Errorf("expected 2 arguments but had: %v", len(args)) } collection := toolbox.AsSlice(args[0]) for i, candidate := range collection { if candidate == args[1] || toolbox.AsString(candidate) == toolbox.AsString(args[1]) { return i, nil } } return -1, nil } func TestParseExpression(t *testing.T) { var useCases = []struct { description string aMap Map expression string expected interface{} }{ { description: "simple variable", aMap: Map(map[string]interface{}{ "k1": 123, }), expression: "$k1", expected: 123, }, { description: "simple enclosed variable", aMap: Map(map[string]interface{}{ "k1": 123, }), expression: "${k1}", expected: 123, }, { description: "simple embedding", aMap: Map(map[string]interface{}{ "k1": 123, }), expression: "abc $k1 xyz", expected: "abc 123 xyz", }, { description: "simple embedding", aMap: Map(map[string]interface{}{ "k1": 123, }), expression: "abc $k1/xyz", expected: "abc 123/xyz", }, { description: "double embedding", aMap: Map(map[string]interface{}{ "k1": 123, "k2": 88, }), expression: "abc $k1 xyz $k2 ", expected: "abc 123 xyz 88 ", }, { description: "enclosing ", aMap: Map(map[string]interface{}{ "k1": 123, "k2": 88, }), expression: "abc ${k1} xyz $k2 ", expected: "abc 123 xyz 88 ", }, { description: "enclosing and partialy unexpanded", aMap: Map(map[string]interface{}{ "k1": 123, "k2": 88, }), expression: " $z1 abc ${k1} xyz $k2 ", expected: " $z1 abc 123 xyz 88 ", }, { description: "sub key access", aMap: Map(map[string]interface{}{ "k2": map[string]interface{}{ "z": 111, "x": 333, }, }), expression: "abc ${k2.z} xyz $k2.x/ ", expected: "abc 111 xyz 333/ ", }, { description: "slice & nested access", aMap: Map(map[string]interface{}{ "array": []interface{}{ map[string]interface{}{ "z": 111, "x": map[string]interface{}{ "k": 444, }, "y": []interface{}{"a", "b"}, }, }, }), expression: "abc $array[0].z $array[0].y[0]* !${array[0].x.k}#$array[0].x.k", expected: "abc 111 a* !444#444", }, { description: "slice with index variable", aMap: Map(map[string]interface{}{ "i": 1, "array": []interface{}{ 111, 222, 333, }, }), expression: "$array[$i]", expected: 222, }, { description: "slice with index variable", aMap: Map(map[string]interface{}{ "i": 2, "array": []interface{}{ 111, 222, 333, }, }), expression: "$array[${i}]", expected: 333, }, { description: "slice with index variable", aMap: Map(map[string]interface{}{ "i": 2, "array": []interface{}{ 111, 222, 333, }, }), expression: "${array[${i}]}", expected: 333, }, { description: "variable func", aMap: Map(map[string]interface{}{ "f": func(key interface{}, state Map) (interface{}, error) { return "test " + toolbox.AsString(key), nil }, }), expression: "$f(123)", expected: "test 123", }, { description: "variable func", aMap: Map(map[string]interface{}{ "f": func(key interface{}, state Map) (interface{}, error) { return "test " + toolbox.AsString(key), nil }, }), expression: "a $f(123) b", expected: "a test 123 b", }, { description: "variable func", aMap: Map(map[string]interface{}{ "f": func(key interface{}, state Map) (interface{}, error) { return "test " + toolbox.AsString(key), nil }, }), expression: "a ${f(123)} b", expected: "a test 123 b", }, { description: "variable func with unexpanded variables", aMap: Map(map[string]interface{}{ "f": func(key interface{}, state Map) (interface{}, error) { return "test " + toolbox.AsString(key), nil }, }), expression: "${a()} ${f(123)} $b()", expected: "${a()} test 123 $b()", }, { description: "variable func with slice arguments", aMap: Map(map[string]interface{}{ "f": func(args interface{}, state Map) (interface{}, error) { aSlice := toolbox.AsSlice(args) textSlice := []string{} for _, item := range aSlice { textSlice = append(textSlice, toolbox.AsString(item)) } return strings.Join(textSlice, ":"), nil }, }), expression: `! $f(["a", "b", "c"]) !`, expected: "! a:b:c !", }, { description: "variable func with aMap arguments", aMap: Map(map[string]interface{}{ "f": func(args interface{}, state Map) (interface{}, error) { aMap := toolbox.AsMap(args) aSlice := []string{} for k, v := range aMap { aSlice = append(aSlice, toolbox.AsString(fmt.Sprintf("%v->%v", k, v))) } sort.Strings(aSlice) return strings.Join(aSlice, ":"), nil }, }), expression: `! $f({"a":1, "b":2, "c":3}) !`, expected: "! a->1:b->2:c->3 !", }, { description: "slice element shift", aMap: Map(map[string]interface{}{ "s": []interface{}{3, 2, 1}, }), expression: `! $<-s ${<-s} !`, expected: "! 3 2 !", }, { description: "element inc", aMap: Map(map[string]interface{}{ "i": 2, "j": 5, }), expression: `!${i++}/${i}/${++i}!`, expected: "!2/3/4!", }, { description: "basic arithmetic", aMap: Map(map[string]interface{}{ "i": 1, "j": 2, "k": 0.4, }), expression: `${(i + j) / 2}`, expected: 1.5, }, { description: "enclosed basic arithmetic", aMap: Map(map[string]interface{}{ "i": 1, "j": 2, "k": 0.4, }), expression: `z${(i + j) / 2}z`, expected: "z1.5z", }, { description: "multi arithmetic", aMap: Map(map[string]interface{}{ "i": 1, "j": 2, "k": 0.4, }), expression: `${10 + 1 - 2}`, expected: 9, }, { description: "sub attribute arithmetic", aMap: Map(map[string]interface{}{ "i": 1, "j": 2, "k": map[string]interface{}{ "z": 0.4, }, "s": []interface{}{10}, }), expression: `${k.z * s[0]}`, expected: 4, }, { description: "unexpanded ", aMap: Map(map[string]interface{}{ "index": 1, }), expression: `${index}*`, expected: "1*", }, { description: "unexpanded ", aMap: Map(map[string]interface{}{ "i": 1, }), expression: `[]Orders,`, expected: "[]Orders,", }, { description: "unexpanded tags", aMap: Map(map[string]interface{}{ "tag": "Root", "tagId": "Root", }), expression: `[]Orders,Id,Name,LineItems,SubTotal`, expected: "[]Orders,Id,Name,LineItems,SubTotal", }, { description: "unexpanded dolar", aMap: Map(map[string]interface{}{ "tag": "Root", }), expression: `$`, expected: "$", }, { description: "unexpanded enclosed dolar", aMap: Map(map[string]interface{}{ "tag": "Root", }), expression: `a/$/z`, expected: "a/$/z", }, { description: "udf with text argument", aMap: Map(map[string]interface{}{ "r": func(key interface{}, state Map) (interface{}, error) { return true, nil }, }), expression: `$r(use_cases/001_event_processing_use_case/skip.txt):true`, expected: "true:true", }, { description: "int conversion", aMap: Map(map[string]interface{}{ "AsInt": func(source interface{}, state Map) (interface{}, error) { return toolbox.AsInt(source), nil }, }), expression: `z $AsInt(3434)`, expected: "z 3434", }, { description: "int conversion", aMap: Map(map[string]interface{}{ "dailyCap": 100, "overallCap": 2, "AsFloat": func(source interface{}, state Map) (interface{}, error) { return toolbox.AsFloat(source), nil }, }), expression: `{ "DAILY_CAP": "$AsFloat($dailyCap)" }`, expected: "{\n\t\t \"DAILY_CAP\": \"100\"\n\t\t}", }, { description: "post increment", aMap: map[string]interface{}{ "i": 0, "z": 3, }, expression: "$i++ $i $z++ $z", expected: "0 1 3 4", }, { description: "pre increment", aMap: map[string]interface{}{ "i": 10, "z": 20, }, expression: "$++i $i $++z $z", expected: "11 11 21 21", }, { description: "arguments as text glitch", aMap: map[string]interface{}{ "f": func(source interface{}, state Map) (interface{}, error) { return source, nil }, }, expression: "#$f(554257_popularmechanics.com)#", expected: "#554257_popularmechanics.com#", }, { description: "embedded UDF expression", aMap: map[string]interface{}{ "IndexOf": IndexOf, "collection": []interface{}{"abc", "xtz"}, "key": "abc", }, expression: `$IndexOf($collection, $key)`, expected: 0, }, { description: "embedded UDF expression with literal", aMap: map[string]interface{}{ "IndexOf": IndexOf, "collection": []interface{}{"abc", "xtz"}, "key": "abc", }, expression: `$IndexOf($collection, xtz)`, expected: 1, }, { description: "multi udf neating", aMap: map[string]interface{}{ "IndexOf": IndexOf, "collection": []interface{}{"abc", "xtz"}, "key": "abc", }, expression: `$IndexOf($collection, xtz)`, expected: 1, }, { description: "unresolved expression", aMap: map[string]interface{}{ "IndexOf": IndexOf, "collection": []interface{}{"abc", "xtz"}, "key": "abc", }, expression: `${$appPath}/hello/main.zip`, expected: `${$appPath}/hello/main.zip`, }, { description: "resolved expression", aMap: map[string]interface{}{ "appPath": "/abc/a", }, expression: `${appPath}/hello/main.zip`, expected: `/abc/a/hello/main.zip`, }, { description: "byte extraction", aMap: map[string]interface{}{ "Payload": []byte{ 34, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 34, }, "AsString": func(source interface{}, state Map) (interface{}, error) { return toolbox.AsString(source), nil }, }, expression: `$AsString($Payload)`, expected: `"Hello World"`, }, } //$Join($AsCollection($Cat($env.APP_HOME/app-config/schema/go/3.json)), “,”) for _, useCase := range useCases { var expandHandler = func(expression string, isUDF bool, argument interface{}) (interface{}, bool) { result, has := useCase.aMap.GetValue(string(expression[1:])) if isUDF { if udf, ok := result.(func(interface{}, Map) (interface{}, error)); ok { expandedArgs := useCase.aMap.expandArgumentsExpressions(argument) if toolbox.IsString(expandedArgs) && toolbox.IsStructuredJSON(toolbox.AsString(expandedArgs)) { if evaluated, err := toolbox.JSONToInterface(toolbox.AsString(expandedArgs)); err == nil { expandedArgs = evaluated } } result, err := udf(expandedArgs, nil) return result, err == nil } } return result, has } actual := Parse(useCase.expression, expandHandler) if !assert.Equal(t, useCase.expected, actual, useCase.description) { fmt.Printf("!%v!\n", actual) } } } toolbox-0.33.2/data/udf/000077500000000000000000000000001374110251100147475ustar00rootroot00000000000000toolbox-0.33.2/data/udf/README.md000066400000000000000000000037241374110251100162340ustar00rootroot00000000000000# Expandable Collection User Defined Function ### Usage #### Data substitution ```go aMap := data.NewMap(); aMap.Put("ts", "2015-02-11") udf.Register(aMap) expanded := aMap.ExpandAsText(`$FormatTime($ts, "yyyy")`) ``` #### Data node selection ```go holder := data.NewMap() collection := data.NewCollection() collection.Push(map[string]interface{}{ "amount": 2, "id":2, "name":"p1", "vendor":"v1", }) collection.Push(map[string]interface{}{ "amount": 12, "id":3, "name":"p2", "vendor":"v2", }) holder.SetValue("node1.obj", collection) records, err := Select([]interface{}{"node1/obj/*", "id", "name:product"}, holder) ``` #### The list of defined UDFs - Length, Len returns length of slice, map or string - AsMap - convert source into a map, it accepts data structure, or JSON, YAML literal - AsCollection - convert source into a slice, it accepts data structure, or JSON, YAML literal - AsData - convert source into a map or slice, it accepts data structure, or JSON, YAML literal - AsInt - convert source into a an int - AsFloat - convert source into a a float - AsBool - convert source into a boolean - AsNumber - converts to either int or float - FormatTime, takes two arguments, date or now, followed by java style date format - Values - returns map values - Keys - return map keys - IndexOf - returns index of matched slice element - Join - join slice element with supplied separator - Split - split text by separator - Sum - sums values for matched Path, i.e. $Sum('node1/obj/*/amount') - Count - counts values for matched Path, i.e. $Sum('node1/obj/*/amount') - Select - selects attribute for matched path, i.e $Select("node1/obj/*", "id", "name:product") - QueryEscape - url escape - QueryUnescape - url unescape - Base64Encode - Base64DecodeText - TrimSpace - Elapsed elapsed time - Rand - Replace - ToLower - ToUpper - AsNewLineDelimitedJSONtoolbox-0.33.2/data/udf/conversion.go000066400000000000000000000114601374110251100174650ustar00rootroot00000000000000package udf import ( "fmt" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "gopkg.in/yaml.v2" "strings" ) //AsInt converts source into int func AsInt(source interface{}, state data.Map) (interface{}, error) { return toolbox.ToInt(source) } //AsInt converts source into int func AsString(source interface{}, state data.Map) (interface{}, error) { isNonByteSlice := toolbox.IsSlice(source) if isNonByteSlice { if _, isByteArray := source.([]byte); isByteArray { isNonByteSlice = false } } if isNonByteSlice || toolbox.IsMap(source) || toolbox.IsStruct(source) { text, err := toolbox.AsJSONText(source) if err == nil { return text, nil } } if toolbox.IsNumber(source) { source, _ = AsNumber(source, state) } return toolbox.AsString(source), nil } //Lower converts string to lower case func ToLower(source interface{}, state data.Map) (interface{}, error) { return strings.ToLower(toolbox.AsString(source)), nil } //Lower converts string to upper case func ToUpper(source interface{}, state data.Map) (interface{}, error) { return strings.ToUpper(toolbox.AsString(source)), nil } //AsFloat converts source into float64 func AsFloat(source interface{}, state data.Map) (interface{}, error) { return toolbox.AsFloat(source), nil } //AsFloat32 converts source into float32 func AsFloat32(source interface{}, state data.Map) (interface{}, error) { return float32(toolbox.AsFloat(source)), nil } //AsFloat32 converts source into float32 func AsFloat32Ptr(source interface{}, state data.Map) (interface{}, error) { result := float32(toolbox.AsFloat(source)) return &result, nil } //AsBool converts source into bool func AsBool(source interface{}, state data.Map) (interface{}, error) { return toolbox.AsBoolean(source), nil } //AsMap converts source into map func AsMap(source interface{}, state data.Map) (interface{}, error) { if source == nil || toolbox.IsMap(source) { return source, nil } source = convertToTextIfNeeded(source) if text, ok := source.(string); ok { text = strings.TrimSpace(text) aMap := map[string]interface{}{} if strings.HasPrefix(text, "{") || strings.HasSuffix(text, "}") { if err := toolbox.NewJSONDecoderFactory().Create(strings.NewReader(text)).Decode(&aMap); err != nil { return nil, err } } if err := yaml.NewDecoder(strings.NewReader(toolbox.AsString(source))).Decode(&aMap); err != nil { return nil, err } return toolbox.NormalizeKVPairs(aMap) } return toolbox.ToMap(source) } //AsCollection converts source into a slice func AsCollection(source interface{}, state data.Map) (interface{}, error) { if source == nil || toolbox.IsSlice(source) { return source, nil } source = convertToTextIfNeeded(source) if text, ok := source.(string); ok { text = strings.TrimSpace(text) if strings.HasPrefix(text, "[") || strings.HasSuffix(text, "[") { aSlice := []interface{}{} if err := toolbox.NewJSONDecoderFactory().Create(strings.NewReader(text)).Decode(&aSlice); err != nil { return nil, err } } var aSlice interface{} if err := yaml.NewDecoder(strings.NewReader(toolbox.AsString(source))).Decode(&aSlice); err != nil { return nil, err } return toolbox.NormalizeKVPairs(aSlice) } return nil, fmt.Errorf("unable convert to slice, unsupported type: %T", source) } //AsData converts source into map or slice func AsData(source interface{}, state data.Map) (interface{}, error) { if source == nil || toolbox.IsMap(source) || toolbox.IsSlice(source) { return source, nil } var aData interface{} source = convertToTextIfNeeded(source) if text, ok := source.(string); ok { text = strings.TrimSpace(text) if strings.HasPrefix(text, "[") || strings.HasSuffix(text, "[") || strings.HasPrefix(text, "{") || strings.HasSuffix(text, "}") { if err := toolbox.NewJSONDecoderFactory().Create(strings.NewReader(text)).Decode(&aData); err != nil { return nil, err } } if err := yaml.NewDecoder(strings.NewReader(toolbox.AsString(source))).Decode(&aData); err != nil { return nil, err } return toolbox.NormalizeKVPairs(aData) } return source, nil } func convertToTextIfNeeded(data interface{}) interface{} { if data == nil { return data } if bs, ok := data.([]byte); ok { return string(bs) } return data } //AsJSON converts source to JSON func AsJSON(source interface{}, state data.Map) (interface{}, error) { return toolbox.AsIndentJSONText(source) } //Type returns source type func Type(source interface{}, state data.Map) (interface{}, error) { return fmt.Printf("%T", source) } //AsStringMap returns map[string]string func AsStringMap(source interface{}, state data.Map) (interface{}, error) { if source == nil && !toolbox.IsMap(source) { return nil, fmt.Errorf("not a map") } var result = make(map[string]string) for k, v := range toolbox.AsMap(source) { result[k] = toolbox.AsString(v) } return result, nil } toolbox-0.33.2/data/udf/conversion_test.go000066400000000000000000000054271374110251100205320ustar00rootroot00000000000000package udf import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox/data" "log" "reflect" "testing" ) func Test_AsBool(t *testing.T) { ok, err := AsBool("true", nil) assert.Nil(t, err) assert.Equal(t, true, ok) } func Test_AsFloat(t *testing.T) { value, err := AsFloat(0.3, nil) assert.Nil(t, err) assert.Equal(t, 0.3, value) } func Test_AsInt(t *testing.T) { value, err := AsInt(4.3, nil) assert.Nil(t, err) assert.Equal(t, 4, value) } func Test_AsMap(t *testing.T) { { var aMap, err = AsMap(map[string]interface{}{}, nil) assert.Nil(t, err) assert.NotNil(t, aMap) } { var aMap, err = AsMap("{\"abc\":1}", nil) assert.Nil(t, err) assert.NotNil(t, aMap) } { var aMap, err = AsMap(`abc: 1`, nil) assert.Nil(t, err) assert.NotNil(t, aMap) } { _, err := AsMap("{\"abc\":1, \"a}", nil) assert.NotNil(t, err) } } func Test_AsCollection(t *testing.T) { { var aSlice, err = AsCollection([]interface{}{1}, nil) assert.Nil(t, err) assert.NotNil(t, aSlice) } { var aSlice, err = AsCollection("[1,2]", nil) assert.Nil(t, err) assert.NotNil(t, aSlice) } { var aSlice, err = AsCollection(` - 1 - 2`, nil) assert.Nil(t, err) assert.NotNil(t, aSlice) } { _, err := AsCollection("[\"a,2]", nil) assert.NotNil(t, err) } { var aMap, err = AsData(`abc: 1`, nil) assert.Nil(t, err) assert.NotNil(t, aMap) } } func Test_AsData(t *testing.T) { { var aSlice, err = AsData("[1,2]", nil) assert.Nil(t, err) assert.NotNil(t, aSlice) } { var aMap, err = AsData("{\"abc\":1}", nil) assert.Nil(t, err) assert.NotNil(t, aMap) } } func Test_YamlAsCollection(t *testing.T) { var YAML = `- Requests: - URL: http://localhost:5000 Method: GET Header: aHeader: - "myField=a-value; path=/; domain=localhost; Expires=Tue, 19 Jan 2038 03:14:07 GMT;" someOtherHeader: - "CP=RTO" Body: "hey there" Cookies: - Name: aHeader Value: a-value DYAMLomain: "localhost" Expires: "2023-12-16T20:17:38Z" RawExpires: Sat, 16 Dec 2023 20:17:38 GMT` expanded, err := AsCollection(YAML, nil) if !assert.Nil(t, err) { log.Fatal(err) } assert.Equal(t, reflect.Slice, reflect.TypeOf(expanded).Kind()) } func Test_YamlAsMap(t *testing.T) { YAML := `default: &default Name: Jack person: <<: *default Name: Bob` expanded, err := AsCollection(YAML, nil) assert.Nil(t, err) assert.NotNil(t, expanded) } func Test_AsString(t *testing.T) { aMap := data.NewMap() Register(aMap) aMap.Put("k0", true) expanded := aMap.ExpandAsText(" $AsString(${k0})") assert.EqualValues(t, "true", expanded) //64 bit int aMap.Put("k1", 2323232323223) expanded = aMap.ExpandAsText(" $AsString(${k1})") assert.EqualValues(t, "2323232323223", expanded) } toolbox-0.33.2/data/udf/doc.go000066400000000000000000000001411374110251100160370ustar00rootroot00000000000000package udf //Doc udf documentation type Doc struct { Description string Example string } toolbox-0.33.2/data/udf/load.go000066400000000000000000000020551374110251100162170ustar00rootroot00000000000000package udf import ( "encoding/json" "github.com/pkg/errors" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "io/ioutil" ) //LoadJSON loads new line delimited or regular JSON into data structure func LoadJSON(source interface{}, state data.Map) (interface{}, error) { location := toolbox.AsString(source) if location == "" { return nil, errors.New("location was empty at LoadJSON") } data, err := ioutil.ReadFile(location) if err != nil { return nil, errors.Wrapf(err, "failed to load: %v", location) } JSON := string(data) if toolbox.IsNewLineDelimitedJSON(JSON) { slice, err := toolbox.NewLineDelimitedJSON(JSON) if err != nil { return nil, err } var result = make([]interface{}, 0) toolbox.ProcessSlice(slice, func(item interface{}) bool { if item == nil { return true } if toolbox.IsMap(item) && len(toolbox.AsMap(item)) == 0 { return true } result = append(result, item) return true }) return result, nil } var result interface{} err = json.Unmarshal(data, &result) return result, err } toolbox-0.33.2/data/udf/register.go000066400000000000000000000025341374110251100171260ustar00rootroot00000000000000package udf import "github.com/viant/toolbox/data" //Register registers defined in this package UDFs func Register(aMap data.Map) { aMap.Put("AsInt", AsInt) aMap.Put("AsString", AsString) aMap.Put("AsFloat", AsFloat) aMap.Put("AsFloat32", AsFloat32) aMap.Put("AsFloat32Ptr", AsFloat32Ptr) aMap.Put("AsBool", AsBool) aMap.Put("AsMap", AsMap) aMap.Put("AsData", AsData) aMap.Put("AsCollection", AsCollection) aMap.Put("AsJSON", AsJSON) aMap.Put("Type", Type) aMap.Put("Join", Join) aMap.Put("Split", Split) aMap.Put("Keys", Keys) aMap.Put("Values", Values) aMap.Put("Length", Length) aMap.Put("Len", Length) aMap.Put("IndexOf", IndexOf) aMap.Put("FormatTime", FormatTime) aMap.Put("QueryEscape", QueryEscape) aMap.Put("QueryUnescape", QueryUnescape) aMap.Put("Base64Encode", Base64Encode) aMap.Put("Base64Decode", Base64Decode) aMap.Put("Base64DecodeText", Base64DecodeText) aMap.Put("TrimSpace", TrimSpace) aMap.Put("Elapsed", Elapsed) aMap.Put("Sum", Sum) aMap.Put("Count", Count) aMap.Put("AsNumber", AsNumber) aMap.Put("Select", Select) aMap.Put("Rand", Rand) aMap.Put("Concat", Concat) aMap.Put("Merge", Merge) aMap.Put("AsStringMap", AsStringMap) aMap.Put("Replace", Replace) aMap.Put("ToLower", ToLower) aMap.Put("ToUpper", ToUpper) aMap.Put("AsNewLineDelimitedJSON", AsNewLineDelimitedJSON) aMap.Put("LoadJSON", LoadJSON) } toolbox-0.33.2/data/udf/time.go000066400000000000000000000043161374110251100162400ustar00rootroot00000000000000package udf import ( "fmt" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "time" ) //FormatTime return formatted time, it takes an array of arguments, the first is time express, or now followed by java style time format, optional timezone and truncate format . func FormatTime(source interface{}, state data.Map) (interface{}, error) { if !toolbox.IsSlice(source) { return nil, fmt.Errorf("unable to run FormatTime: expected %T, but had: %T", []interface{}{}, source) } aSlice := toolbox.AsSlice(source) if len(aSlice) < 2 { return nil, fmt.Errorf("unable to run FormatTime, expected 2 parameters, but had: %v", len(aSlice)) } var err error var timeText = toolbox.AsString(aSlice[0]) var timeFormat = toolbox.AsString(aSlice[1]) var timeLayout = toolbox.DateFormatToLayout(timeFormat) var timeValue *time.Time timeValue, err = toolbox.TimeAt(timeText) if err != nil { timeValue, err = toolbox.ToTime(aSlice[0], timeLayout) } if err != nil { return nil, err } if len(aSlice) > 2 && aSlice[2] != "" { timeLocation, err := time.LoadLocation(toolbox.AsString(aSlice[2])) if err != nil { return nil, err } timeInLocation := timeValue.In(timeLocation) timeValue = &timeInLocation } if len(aSlice) > 3 { switch aSlice[3] { case "weekday": return timeValue.Weekday(), nil default: truncFromat := toolbox.DateFormatToLayout(toolbox.AsString(aSlice[3])) if ts, err := time.Parse(truncFromat, timeValue.Format(truncFromat));err == nil { timeValue = &ts } } } return timeValue.Format(timeLayout), nil } //Elapsed returns elapsed time func Elapsed(source interface{}, state data.Map) (interface{}, error) { inThePast, err := toolbox.ToTime(source, time.RFC3339) if err != nil { return nil, err } elapsed := time.Now().Sub(*inThePast).Truncate(time.Second) days := elapsed / (24 * time.Hour) hours := int(elapsed.Hours()) % 24 min := int(elapsed.Minutes()) % 60 sec := int(elapsed.Seconds()) % 60 result := "" if days > 0 { result = fmt.Sprintf("%dd", int(days)) } if result == "" && hours > 0 { result += fmt.Sprintf("%dh", hours) } if result == "" && min > 0 { result += fmt.Sprintf("%dm", min) } result += fmt.Sprintf("%ds", sec) return result, nil } toolbox-0.33.2/data/udf/time_test.go000066400000000000000000000027231374110251100172770ustar00rootroot00000000000000package udf import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "strings" "testing" "time" ) func Test_FormatTime(t *testing.T) { { value, err := FormatTime([]interface{}{"now", "yyyy"}, nil) assert.Nil(t, err) now := time.Now() assert.Equal(t, now.Year(), toolbox.AsInt(value)) } { value, err := FormatTime([]interface{}{"2015-02-11", "yyyy"}, nil) assert.Nil(t, err) assert.Equal(t, 2015, toolbox.AsInt(value)) } { _, err := FormatTime([]interface{}{"2015-02-11"}, nil) assert.NotNil(t, err) } { _, err := FormatTime([]interface{}{"201/02/11 2", "y-d"}, nil) assert.NotNil(t, err) } { _, err := FormatTime("a", nil) assert.NotNil(t, err) } { value, err := FormatTime([]interface{}{"now", "yyyy", "UTC"}, nil) assert.Nil(t, err) now := time.Now() assert.Equal(t, now.Year(), toolbox.AsInt(value)) } { aMap := data.NewMap() aMap.Put("ts", "2015-02-11") Register(aMap) expanded := aMap.ExpandAsText(`$FormatTime($ts, "yyyy")`) assert.Equal(t, "2015", expanded) } { value, err := FormatTime([]interface{}{"now", "yyyy-MM-dd HH:mm:ss", "", "yyyy-MM"}, nil) assert.Nil(t, err) now := time.Now() assert.True(t, strings.HasPrefix(toolbox.AsString(value), toolbox.AsString(now.Year()))) } } func TestElapsed(t *testing.T) { { value, err := Elapsed(time.Now().Add(-time.Hour).Format(time.RFC3339), nil) assert.Nil(t, err) assert.Equal(t, "1h0s", value) } } toolbox-0.33.2/data/udf/util.go000066400000000000000000000313741374110251100162630ustar00rootroot00000000000000package udf import ( "encoding/base64" "encoding/json" "fmt" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "math/rand" "net/url" "strings" "time" ) //Length returns length of slice or string func Length(source interface{}, state data.Map) (interface{}, error) { if toolbox.IsSlice(source) { return len(toolbox.AsSlice(source)), nil } if toolbox.IsMap(source) { return len(toolbox.AsMap(source)), nil } if text, ok := source.(string); ok { if strings.HasPrefix(text, "$") { return nil, fmt.Errorf("unexpanded variable: %v", text) } return len(text), nil } return 0, nil } //Replace replaces text with old and new fragments func Replace(source interface{}, state data.Map) (interface{}, error) { var args []interface{} if ! toolbox.IsSlice(source) { return nil, fmt.Errorf("expacted %T, but had %T", args, source) } args = toolbox.AsSlice(source) if len(args) < 3 { return nil, fmt.Errorf("expected 3 arguments (text, old, new), but had: %v" , len(args)) } text := toolbox.AsString(args[0]) old := toolbox.AsString(args[1]) new := toolbox.AsString(args[2]) count := strings.Count(text, old) return strings.Replace(text, old, new, count), nil } // Join joins slice by separator func Join(args interface{}, state data.Map) (interface{}, error) { if !toolbox.IsSlice(args) { return nil, fmt.Errorf("expected 2 arguments but had: %T", args) } arguments := toolbox.AsSlice(args) if len(arguments) != 2 { return nil, fmt.Errorf("expected 2 arguments but had: %v", len(arguments)) } if !toolbox.IsSlice(arguments[0]) { return nil, fmt.Errorf("expected 1st arguments as slice but had: %T", arguments[0]) } var result = make([]string, 0) toolbox.CopySliceElements(arguments[0], &result) return strings.Join(result, toolbox.AsString(arguments[1])), nil } // Split split text to build a slice func Split(args interface{}, state data.Map) (interface{}, error) { if !toolbox.IsSlice(args) { return nil, fmt.Errorf("expected 2 arguments but had: %T", args) } arguments := toolbox.AsSlice(args) if len(arguments) != 2 { return nil, fmt.Errorf("expected 2 arguments but had: %v", len(arguments)) } if !toolbox.IsString(arguments[0]) { return nil, fmt.Errorf("expected 1st arguments as string but had: %T", arguments[0]) } result := strings.Split(toolbox.AsString(arguments[0]), toolbox.AsString(arguments[1])) for i := range result { result[i] = strings.TrimSpace(result[i]) } return result, nil } //Keys returns keys of the supplied map func Keys(source interface{}, state data.Map) (interface{}, error) { aMap, err := AsMap(source, state) if err != nil { return nil, err } var result = make([]interface{}, 0) err = toolbox.ProcessMap(aMap, func(key, value interface{}) bool { result = append(result, key) return true }) if err != nil { return nil, err } return result, nil } //Values returns values of the supplied map func Values(source interface{}, state data.Map) (interface{}, error) { aMap, err := AsMap(source, state) if err != nil { return nil, err } var result = make([]interface{}, 0) err = toolbox.ProcessMap(aMap, func(key, value interface{}) bool { result = append(result, value) return true }) if err != nil { return nil, err } return result, nil } //IndexOf returns index of the matched slice elements or -1 func IndexOf(source interface{}, state data.Map) (interface{}, error) { if !toolbox.IsSlice(source) { return nil, fmt.Errorf("expected arguments but had: %T", source) } args := toolbox.AsSlice(source) if len(args) != 2 { return nil, fmt.Errorf("expected 2 arguments but had: %v", len(args)) } if toolbox.IsString(args[0]) { return strings.Index(toolbox.AsString(args[0]), toolbox.AsString(args[1])), nil } collection, err := AsCollection(args[0], state) if err != nil { return nil, err } for i, candidate := range toolbox.AsSlice(collection) { if candidate == args[1] || toolbox.AsString(candidate) == toolbox.AsString(args[1]) { return i, nil } } return -1, nil } //Base64Decode encodes source using base64.StdEncoding func Base64Encode(source interface{}, state data.Map) (interface{}, error) { if source == nil { return "", nil } switch value := source.(type) { case string: return base64.StdEncoding.EncodeToString([]byte(value)), nil case []byte: return base64.StdEncoding.EncodeToString(value), nil default: if toolbox.IsMap(source) || toolbox.IsSlice(source) { encoded, err := json.Marshal(source) fmt.Printf("%s %v\n", encoded, err) if err == nil { return base64.StdEncoding.EncodeToString(encoded), nil } } return nil, fmt.Errorf("unsupported type: %T", source) } } //Base64Decode decodes source using base64.StdEncoding func Base64Decode(source interface{}, state data.Map) (interface{}, error) { if source == nil { return "", nil } switch value := source.(type) { case string: return base64.StdEncoding.DecodeString(value) case []byte: return base64.StdEncoding.DecodeString(string(value)) default: return nil, fmt.Errorf("unsupported type: %T", source) } } //Base64DecodeText decodes source using base64.StdEncoding to string func Base64DecodeText(source interface{}, state data.Map) (interface{}, error) { decoded, err := Base64Decode(source, state) if err != nil { return nil, err } return toolbox.AsString(decoded), nil } //QueryEscape returns url escaped text func QueryEscape(source interface{}, state data.Map) (interface{}, error) { text := toolbox.AsString(source) return url.QueryEscape(text), nil } //QueryUnescape returns url escaped text func QueryUnescape(source interface{}, state data.Map) (interface{}, error) { text := toolbox.AsString(source) return url.QueryUnescape(text) } //TrimSpace returns trims spaces from supplied text func TrimSpace(source interface{}, state data.Map) (interface{}, error) { text := toolbox.AsString(source) return strings.TrimSpace(text), nil } //Count returns count of matched nodes leaf value func Count(xPath interface{}, state data.Map) (interface{}, error) { result, err := aggregate(xPath, state, func(previous, newValue float64) float64 { return previous + 1 }) if err != nil { return nil, err } return AsNumber(result, nil) } //Sum returns sums of matched nodes leaf value func Sum(xPath interface{}, state data.Map) (interface{}, error) { result, err := aggregate(xPath, state, func(previous, newValue float64) float64 { return previous + newValue }) if err != nil { return nil, err } return AsNumber(result, nil) } //Select returns all matched attributes from matched nodes, attributes can be alised with sourcePath:alias func Select(params interface{}, state data.Map) (interface{}, error) { var arguments = make([]interface{}, 0) if toolbox.IsSlice(params) { arguments = toolbox.AsSlice(params) } else { arguments = append(arguments, params) } xPath := toolbox.AsString(arguments[0]) var result = make([]interface{}, 0) attributes := make([]string, 0) for i := 1; i < len(arguments); i++ { attributes = append(attributes, toolbox.AsString(arguments[i])) } err := matchPath(xPath, state, func(matched interface{}) error { if len(attributes) == 0 { result = append(result, matched) return nil } if !toolbox.IsMap(matched) { return fmt.Errorf("expected map for %v, but had %T", xPath, matched) } matchedMap := data.Map(toolbox.AsMap(matched)) var attributeValues = make(map[string]interface{}) for _, attr := range attributes { if strings.Contains(attr, ":") { kvPair := strings.SplitN(attr, ":", 2) value, has := matchedMap.GetValue(kvPair[0]) if !has { continue } attributeValues[kvPair[1]] = value } else { value, has := matchedMap.GetValue(attr) if !has { continue } attributeValues[attr] = value } } result = append(result, attributeValues) return nil }) return result, err } //AsNumber return int or float func AsNumber(value interface{}, state data.Map) (interface{}, error) { floatValue := toolbox.AsFloat(value) if float64(int(floatValue)) == floatValue { return int(floatValue), nil } return floatValue, nil } //Aggregate applies an aggregation function to matched path func aggregate(xPath interface{}, state data.Map, agg func(previous, newValue float64) float64) (float64, error) { var result = 0.0 if state == nil { return 0.0, fmt.Errorf("state was empty") } err := matchPath(toolbox.AsString(xPath), state, func(value interface{}) error { if value == nil { return nil } floatValue, err := toolbox.ToFloat(value) if err != nil { return err } result = agg(result, floatValue) return nil }) return result, err } func matchPath(xPath string, state data.Map, handler func(value interface{}) error) error { fragments := strings.Split(toolbox.AsString(xPath), "/") var node = state var nodeValue interface{} for i, part := range fragments { isLast := i == len(fragments)-1 if isLast { if part == "*" { if toolbox.IsSlice(nodeValue) { for _, item := range toolbox.AsSlice(nodeValue) { if err := handler(item); err != nil { return err } } return nil } else if toolbox.IsMap(nodeValue) { for _, item := range toolbox.AsMap(nodeValue) { if err := handler(item); err != nil { return err } } } return handler(nodeValue) } if !node.Has(part) { break } if err := handler(node.Get(part)); err != nil { return err } continue } if part != "*" { nodeValue = node.Get(part) if nodeValue == nil { break } if toolbox.IsMap(nodeValue) { node = toolbox.AsMap(nodeValue) continue } if toolbox.IsSlice(nodeValue) { continue } break } if nodeValue == nil { break } subXPath := strings.Join(fragments[i+1:], "/") if toolbox.IsSlice(nodeValue) { aSlice := toolbox.AsSlice(nodeValue) for _, item := range aSlice { if toolbox.IsMap(item) { if err := matchPath(subXPath, toolbox.AsMap(item), handler); err != nil { return err } continue } return fmt.Errorf("unsupported path type:%T", item) } } if toolbox.IsMap(nodeValue) { aMap := toolbox.AsMap(nodeValue) for _, item := range aMap { if toolbox.IsMap(item) { if err := matchPath(subXPath, toolbox.AsMap(item), handler); err != nil { return err } continue } return fmt.Errorf("unsupported path type:%T", item) } } break } return nil } //Rand returns random func Rand(params interface{}, state data.Map) (interface{}, error) { source := rand.NewSource(time.Now().UnixNano()) generator := rand.New(source) floatValue := generator.Float64() if params == nil || !toolbox.IsSlice(params) { return floatValue, nil } parameters := toolbox.AsSlice(params) if len(parameters) != 2 { return floatValue, nil } min := toolbox.AsInt(parameters[0]) max := toolbox.AsInt(parameters[1]) return min + int(float64(max-min)*floatValue), nil } //Concat concatenate supplied parameters, parameters func Concat(params interface{}, state data.Map) (interface{}, error) { if params == nil || !toolbox.IsSlice(params) { return nil, fmt.Errorf("invalid signature, expected: $Concat(arrayOrItem1, arrayOrItem2)") } var result = make([]interface{}, 0) parameters := toolbox.AsSlice(params) if len(parameters) == 0 { return result, nil } if toolbox.IsString(parameters[0]) { result := "" for _, item := range parameters { result += toolbox.AsString(item) } return result, nil } for _, item := range parameters { if toolbox.IsSlice(item) { itemSlice := toolbox.AsSlice(item) result = append(result, itemSlice...) continue } result = append(result, item) } return result, nil } //Merge creates a new merged map for supplied maps, (mapOrPath1, mapOrPath2, mapOrPathN) func Merge(params interface{}, state data.Map) (interface{}, error) { if params == nil || !toolbox.IsSlice(params) { return nil, fmt.Errorf("invalid signature, expected: $Merge(map1, map2, override)") } var result = make(map[string]interface{}) parameters := toolbox.AsSlice(params) if len(parameters) == 0 { return result, nil } var ok bool for _, item := range parameters { if toolbox.IsString(item) && state != nil { if item, ok = state.GetValue(toolbox.AsString(item)); !ok { continue } } if !toolbox.IsMap(item) { continue } itemMap := toolbox.AsMap(item) for k, v := range itemMap { result[k] = v } } return result, nil } //AsNewLineDelimitedJSON convers a slice into new line delimited JSON func AsNewLineDelimitedJSON(source interface{}, state data.Map) (interface{}, error) { if source == nil || !toolbox.IsSlice(source) { return nil, fmt.Errorf("invalid signature, expected: $AsNewLineDelimitedJSON([])") } aSlice := toolbox.AsSlice(source) var result = make([]string, 0) for _, item := range aSlice { data, _ := json.Marshal(item) result = append(result, string(data)) } return strings.Join(result, "\n"), nil }toolbox-0.33.2/data/udf/util_test.go000066400000000000000000000120571374110251100173170ustar00rootroot00000000000000package udf import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/data" "testing" ) func Test_Length(t *testing.T) { { value, err := Length(4.3, nil) assert.Nil(t, err) assert.Equal(t, 0, value) } { value, err := Length("abcd", nil) assert.Nil(t, err) assert.Equal(t, 4, value) } { value, err := Length(map[int]int{ 2: 3, 1: 1, 6: 3, }, nil) assert.Nil(t, err) assert.Equal(t, 3, value) } { value, err := Length([]int{1, 2, 3}, nil) assert.Nil(t, err) assert.Equal(t, 3, value) } } func Test_Keys(t *testing.T) { { var keys, err = Keys(map[string]interface{}{}, nil) assert.Nil(t, err) assert.NotNil(t, keys) } { var keys, err = Keys("{\"abc\":1}", nil) assert.Nil(t, err) assert.EqualValues(t, []interface{}{"abc"}, keys) } } func Test_Values(t *testing.T) { { var keys, err = Values(map[string]interface{}{}, nil) assert.Nil(t, err) assert.NotNil(t, keys) } { var keys, err = Values("{\"abc\":1}", nil) assert.Nil(t, err) assert.EqualValues(t, []interface{}{1}, keys) } } func Test_Join(t *testing.T) { { var joined, err = Join([]interface{}{ []interface{}{1, 2, 3}, ",", }, nil) assert.Nil(t, err) assert.NotNil(t, joined) assert.EqualValues(t, "1,2,3", joined) } } func Test_Split(t *testing.T) { { var joined, err = Split([]interface{}{ "abc , zc", ",", }, nil) assert.Nil(t, err) assert.NotNil(t, joined) assert.EqualValues(t, []string{"abc", "zc"}, joined) } } func Test_IndexOf(t *testing.T) { { index, _ := IndexOf([]interface{}{"this is test", "is"}, nil) assert.EqualValues(t, 2, index) } { index, _ := IndexOf([]interface{}{[]string{"this", "is", "test"}, "is"}, nil) assert.EqualValues(t, 1, index) } } func Test_Base64Decode(t *testing.T) { decoded, _ := Base64DecodeText("IkhlbGxvIFdvcmxkIg==", nil) assert.EqualValues(t, `"Hello World"`, decoded) } func TestTrimSpace(t *testing.T) { trimmed, _ := TrimSpace(" erer ", nil) assert.EqualValues(t, `erer`, trimmed) } func TestSum(t *testing.T) { { //sum slice keys var aMap = data.NewMap() var collection = data.NewCollection() collection.Push(map[string]interface{}{ "amount": 2, }) collection.Push(map[string]interface{}{ "amount": 12, }) aMap.SetValue("node1.obj", collection) total, err := Sum("node1/obj/*/amount", aMap) assert.Nil(t, err) assert.Equal(t, 14, total) } { //sum map keys var aMap = data.NewMap() aMap.SetValue("node1.obj.k1.amount", 1) aMap.SetValue("node1.obj.k2.amount", 2) aMap.SetValue("node1.obj.k3.amount", 3) total, err := Sum("node1/obj/*/amount", aMap) assert.Nil(t, err) assert.Equal(t, 6, total) } } func TestCount(t *testing.T) { { //sum slice keys var aMap = data.NewMap() var collection = data.NewCollection() collection.Push(map[string]interface{}{ "amount": 2, }) collection.Push(map[string]interface{}{ "amount": 12, }) aMap.SetValue("node1.obj", collection) total, err := Count("node1/obj/*/amount", aMap) assert.Nil(t, err) assert.Equal(t, 2, total) } { //sum map keys var aMap = data.NewMap() aMap.SetValue("node1.obj.k1.amount", 1) aMap.SetValue("node1.obj.k2.amount", 2) aMap.SetValue("node1.obj.k3.amount", 3) total, err := Count("node1/obj/*/amount", aMap) assert.Nil(t, err) assert.Equal(t, 3, total) } } func TestSelect(t *testing.T) { { //sum slice keys var aMap = data.NewMap() var collection = data.NewCollection() collection.Push(map[string]interface{}{ "amount": 2, "id": 2, "name": "p1", "vendor": "v1", }) collection.Push(map[string]interface{}{ "amount": 12, "id": 3, "name": "p2", "vendor": "v2", }) aMap.SetValue("node1.obj", collection) records, err := Select([]interface{}{"node1/obj/*", "id", "name:product"}, aMap) assert.Nil(t, err) assert.Equal(t, []interface{}{ map[string]interface{}{ "id": 2, "product": "p1", }, map[string]interface{}{ "id": 3, "product": "p2", }, }, records) } } func TestRand(t *testing.T) { { randValue, err := Rand(nil, nil) assert.Nil(t, err) floatValue, err := toolbox.ToFloat(randValue) assert.Nil(t, err) assert.True(t, toolbox.IsFloat(randValue) && floatValue >= 0.0 && floatValue < 1.0) } { randValue, err := Rand([]interface{}{2, 15}, nil) assert.Nil(t, err) intValue, err := toolbox.ToInt(randValue) assert.Nil(t, err) assert.True(t, toolbox.IsInt(randValue) && intValue >= 2 && intValue < 15) } } func TestConcat(t *testing.T) { { result, err := Concat([]interface{}{"a", "b", "c"}, nil) assert.Nil(t, err) assert.EqualValues(t, "abc", result) } { result, err := Concat([]interface{}{[]interface{}{"a", "b"}, "c"}, nil) assert.Nil(t, err) assert.EqualValues(t, []interface{}{"a", "b", "c"}, result) } } func TestMerge(t *testing.T) { { result, err := Merge([]interface{}{map[string]interface{}{ "k1": 1, }, map[string]interface{}{ "k2": 2, }, }, nil) assert.Nil(t, err) assert.EqualValues(t, map[string]interface{}{ "k1": 1, "k2": 2, }, result) } } toolbox-0.33.2/decoder.go000066400000000000000000000142111374110251100152130ustar00rootroot00000000000000package toolbox import ( "bytes" "encoding/csv" "encoding/json" "fmt" "gopkg.in/yaml.v2" "io" "io/ioutil" "strings" ) //Decoder represents a decoder. type Decoder interface { //Decode reads and decodes objects from an input stream. Decode(v interface{}) error } //UnMarshaler represent an struct that can be converted to bytes type UnMarshaler interface { //Unmarshal converts a struct to bytes Unmarshal(data []byte) error } //DecoderFactory create an decoder for passed in input stream type DecoderFactory interface { //Create a decoder for passed in io reader Create(reader io.Reader) Decoder } type jsonDecoderFactory struct{ useNumber bool } func (d jsonDecoderFactory) Create(reader io.Reader) Decoder { decoder := json.NewDecoder(reader) if d.useNumber { decoder.UseNumber() } return decoder } //NewJSONDecoderFactory create a new JSONDecoderFactory func NewJSONDecoderFactory() DecoderFactory { return &jsonDecoderFactory{} } //NewJSONDecoderFactoryWithOption create a new JSONDecoderFactory, it takes useNumber decoder parameter func NewJSONDecoderFactoryWithOption(useNumber bool) DecoderFactory { return &jsonDecoderFactory{useNumber: useNumber} } type unMarshalerDecoderFactory struct { } func (f *unMarshalerDecoderFactory) Create(reader io.Reader) Decoder { return &unMarshalerDecoder{ reader: reader, } } type unMarshalerDecoder struct { reader io.Reader provider func() UnMarshaler } func (d *unMarshalerDecoder) Decode(v interface{}) error { bytes, err := ioutil.ReadAll(d.reader) if err != nil { return fmt.Errorf("failed to decode %v", err) } result, casted := v.(UnMarshaler) if !casted { return fmt.Errorf("failed to decode - unable cast %T to %s", v, result) } return result.Unmarshal(bytes) } //NewUnMarshalerDecoderFactory returns a decoder factory func NewUnMarshalerDecoderFactory() DecoderFactory { return &unMarshalerDecoderFactory{} } //DelimitedRecord represents a delimited record type DelimitedRecord struct { Columns []string Delimiter string Record map[string]interface{} } //IsEmpty returns true if all values are empty or null func (r *DelimitedRecord) IsEmpty() bool { var result = true for _, value := range r.Record { if value == nil { continue } if AsString(value) == "" || AsString(value) == "" { continue } return false } return result } type delimiterDecoder struct { reader io.Reader } func (d *delimiterDecoder) Decode(target interface{}) error { delimitedRecord, ok := target.(*DelimitedRecord) if !ok { return fmt.Errorf("invalid target type, expected %T but had %T", &DelimitedRecord{}, target) } if delimitedRecord.Record == nil { delimitedRecord.Record = make(map[string]interface{}) } var delimiter = delimitedRecord.Delimiter payload, err := ioutil.ReadAll(d.reader) if err != nil { return err } reader := csv.NewReader(bytes.NewReader(payload)) reader.Comma = rune(delimiter[0]) hasColumns := len(delimitedRecord.Columns) > 0 if !hasColumns { delimitedRecord.Columns = make([]string, 0) } record, err := reader.Read() if IsEOFError(err) { return nil } if err != nil { return err } if len(delimitedRecord.Columns) == 0 { for _, field := range record { delimitedRecord.Columns = append(delimitedRecord.Columns, strings.TrimSpace(field)) } } else { for i, field := range record { delimitedRecord.Record[delimitedRecord.Columns[i]] = field } } return nil } type delimiterDecoderFactory struct{} func (f *delimiterDecoderFactory) Create(reader io.Reader) Decoder { return &delimiterDecoder{reader: reader} } //NewDelimiterDecoderFactory returns a new delimitered decoder factory. func NewDelimiterDecoderFactory() DecoderFactory { return &delimiterDecoderFactory{} } type yamlDecoderFactory struct{} func (e yamlDecoderFactory) Create(reader io.Reader) Decoder { return &yamlDecoder{reader} } type yamlDecoder struct { io.Reader } func (d *yamlDecoder) Decode(target interface{}) error { var data, err = ioutil.ReadAll(d.Reader) if err != nil { return fmt.Errorf("failed to read data: %T %v", d.Reader, err) } return yaml.Unmarshal(data, target) } //NewYamlDecoderFactory create a new yaml decoder factory func NewYamlDecoderFactory() DecoderFactory { return &yamlDecoderFactory{} } type flexYamlDecoderFactory struct{} func (e flexYamlDecoderFactory) Create(reader io.Reader) Decoder { return &flexYamlDecoder{reader} } type flexYamlDecoder struct { io.Reader } //normalizeMap normalizes keyValuePairs from map or slice (map with preserved key order) func (d *flexYamlDecoder) normalizeMap(keyValuePairs interface{}, deep bool) (map[string]interface{}, error) { var result = make(map[string]interface{}) if keyValuePairs == nil { return result, nil } err := ProcessMap(keyValuePairs, func(k, value interface{}) bool { var key = AsString(k) //inline map key result[key] = value if deep { if value == nil { return true } if IsMap(value) { if normalized, err := d.normalizeMap(value, deep); err == nil { result[key] = normalized } } else if IsSlice(value) { //yaml style map conversion if applicable aSlice := AsSlice(value) if len(aSlice) == 0 { return true } if IsMap(aSlice[0]) || IsStruct(aSlice[0]) { normalized, err := d.normalizeMap(value, deep) if err == nil { result[key] = normalized } } else if IsSlice(aSlice[0]) { for i, item := range aSlice { itemMap, err := d.normalizeMap(item, deep) if err != nil { return true } aSlice[i] = itemMap } result[key] = aSlice } return true } } return true }) return result, err } func (d *flexYamlDecoder) Decode(target interface{}) error { var data, err = ioutil.ReadAll(d.Reader) if err != nil { return fmt.Errorf("failed to read data: %T %v", d.Reader, err) } aMap := map[string]interface{}{} if err := yaml.Unmarshal(data, &aMap); err != nil { return err } if normalized, err := d.normalizeMap(aMap, true); err == nil { aMap = normalized } return DefaultConverter.AssignConverted(target, aMap) } //NewFlexYamlDecoderFactory create a new yaml decoder factory func NewFlexYamlDecoderFactory() DecoderFactory { return &flexYamlDecoderFactory{} } toolbox-0.33.2/decoder_test.go000066400000000000000000000062271374110251100162620ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "os" "path" "strings" "testing" ) func TestDecoderFactory(t *testing.T) { { reader := strings.NewReader("[1, 2, 3]") decoder := toolbox.NewJSONDecoderFactory().Create(reader) aSlice := make([]int, 0) err := decoder.Decode(&aSlice) assert.Nil(t, err) assert.Equal(t, 3, len(aSlice)) } { reader := strings.NewReader("[1, 2, 3]") decoder := toolbox.NewJSONDecoderFactoryWithOption(true).Create(reader) aSlice := make([]int, 0) err := decoder.Decode(&aSlice) assert.Nil(t, err) assert.Equal(t, 3, len(aSlice)) } } func TestUnMarshalerDecoderFactory(t *testing.T) { reader := strings.NewReader("abc") decoder := toolbox.NewUnMarshalerDecoderFactory().Create(reader) foo := &Foo100{} err := decoder.Decode(foo) assert.Nil(t, err) assert.Equal(t, "abc", foo.Attr) err = decoder.Decode(&Foo101{}) assert.NotNil(t, err) } type Foo100 struct { Attr string } func (m *Foo100) Unmarshal(data []byte) error { m.Attr = string(data) return nil } type Foo101 struct { Attr string } func TestDelimiterDecoderFactory(t *testing.T) { record := &toolbox.DelimitedRecord{ Delimiter: ",", } { decoder := toolbox.NewDelimiterDecoderFactory().Create(strings.NewReader("column1,\"column2\", column3,column4")) err := decoder.Decode(record) if assert.Nil(t, err) { assert.Equal(t, []string{"column1", "column2", "column3", "column4"}, record.Columns) } } { decoder := toolbox.NewDelimiterDecoderFactory().Create(strings.NewReader("1,2,\"ab,cd\",3")) err := decoder.Decode(record) if assert.Nil(t, err) { assert.EqualValues(t, "1", record.Record["column1"]) assert.EqualValues(t, "2", record.Record["column2"]) assert.EqualValues(t, "ab,cd", record.Record["column3"]) assert.EqualValues(t, "3", record.Record["column4"]) } } { decoder := toolbox.NewDelimiterDecoderFactory().Create(strings.NewReader("1,2,\" \"\"location:[\\\"\"BE\\\"\"]\"\" \",3")) err := decoder.Decode(record) if assert.Nil(t, err) { assert.EqualValues(t, "1", record.Record["column1"]) assert.EqualValues(t, "2", record.Record["column2"]) assert.EqualValues(t, " \"location:[\\\"BE\\\"]\" ", record.Record["column3"]) assert.EqualValues(t, "3", record.Record["column4"]) } } } func TestTestYamlDecoder(t *testing.T) { var filename = path.Join(os.Getenv("TMPDIR"), "test.yaml") toolbox.RemoveFileIfExist(filename) defer toolbox.RemoveFileIfExist(filename) var aMap = map[string]interface{}{ "a": 1, "b": "123", "c": []int{1, 3, 6}, } file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644) if assert.Nil(t, err) { err = toolbox.NewYamlEncoderFactory().Create(file).Encode(aMap) assert.Nil(t, err) } var cloneMap = make(map[string]interface{}) file.Close() file, err = os.OpenFile(filename, os.O_RDONLY, 0644) if assert.Nil(t, err) { defer file.Close() err = toolbox.NewYamlDecoderFactory().Create(file).Decode(&cloneMap) if assert.Nil(t, err) { assert.EqualValues(t, aMap["a"], cloneMap["a"]) assert.EqualValues(t, aMap["b"], cloneMap["b"]) assert.EqualValues(t, toolbox.AsSlice(aMap["c"]), cloneMap["c"]) } } } toolbox-0.33.2/doc.go000066400000000000000000000002351374110251100143540ustar00rootroot00000000000000// Package toolbox - useful set of utilities/abstractions developed as part of datastore connectivity and testing (viant/dsc, viant/dsunit). package toolbox toolbox-0.33.2/dumper.go000066400000000000000000000012171374110251100151040ustar00rootroot00000000000000package toolbox import "fmt" //Dump prints passed in data as JSON func Dump(data interface{}) { if text, err := AsJSONText(data); err == nil { fmt.Printf("%v\n", text) return } } //DumpIndent prints passed in data as indented JSON func DumpIndent(data interface{}, removeEmptyKeys bool) error { if IsMap(data) || IsStruct(data) { var aMap = map[string]interface{}{} if err := DefaultConverter.AssignConverted(&aMap, data); err != nil { return err } data = aMap if removeEmptyKeys { data = DeleteEmptyKeys(aMap) } } text, err := AsIndentJSONText(data) if err != nil { return err } fmt.Printf("%v\n", text) return nil } toolbox-0.33.2/encoder.go000066400000000000000000000051511374110251100152300ustar00rootroot00000000000000package toolbox import ( "encoding/json" "fmt" "gopkg.in/yaml.v2" "io" ) //Encoder writes an instance to output stream type Encoder interface { //Encode encodes an instance to output stream Encode(object interface{}) error } //Marshaler represents byte to object converter type Marshaler interface { //Marshal converts bytes to attributes of owner struct Marshal() (data []byte, err error) } //EncoderFactory create an encoder for an output stream type EncoderFactory interface { //Create creates an encoder for an output stream Create(writer io.Writer) Encoder } type jsonEncoderFactory struct{} func (e jsonEncoderFactory) Create(writer io.Writer) Encoder { return json.NewEncoder(writer) } //NewJSONEncoderFactory creates new NewJSONEncoderFactory func NewJSONEncoderFactory() EncoderFactory { return &jsonEncoderFactory{} } type marshalerEncoderFactory struct { } func (f *marshalerEncoderFactory) Create(writer io.Writer) Encoder { return &marshalerEncoder{writer: writer} } type marshalerEncoder struct { writer io.Writer } func (e *marshalerEncoder) Encode(v interface{}) error { result, casted := v.(Marshaler) if !casted { return fmt.Errorf("failed to decode - unable cast %T to %s", v, result) } bytes, err := result.Marshal() if err != nil { return err } var totalByteWritten = 0 var bytesLen = len(bytes) for i := 0; i < bytesLen; i++ { bytesWritten, err := e.writer.Write(bytes[totalByteWritten:]) if err != nil { return fmt.Errorf("failed to write data %v", err) } totalByteWritten = totalByteWritten + bytesWritten if totalByteWritten == bytesLen { break } } if totalByteWritten != bytesLen { return fmt.Errorf("failed to write all data, written %v, expected: %v", totalByteWritten, bytesLen) } return nil } //NewMarshalerEncoderFactory create a new encoder factory for marsheler struct func NewMarshalerEncoderFactory() EncoderFactory { return &marshalerEncoderFactory{} } type yamlEncoderFactory struct{} func (e yamlEncoderFactory) Create(writer io.Writer) Encoder { return &yamlEncoder{writer} } type yamlEncoder struct { io.Writer } //Encode converts source into yaml format to write itto writer func (d *yamlEncoder) Encode(source interface{}) error { data, err := yaml.Marshal(source) if err != nil { return err } var dataSize = len(data) for i := 0; i < dataSize; i++ { written, err := d.Writer.Write(data) if err != nil { return err } if len(data) == written { break } data = data[written:] } return err } //NewYamlEncoderFactory create a new yaml encoder factory func NewYamlEncoderFactory() EncoderFactory { return &yamlEncoderFactory{} } toolbox-0.33.2/encoder_test.go000066400000000000000000000013261374110251100162670ustar00rootroot00000000000000package toolbox_test import ( "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestEncoderFactory(t *testing.T) { buffer := new(bytes.Buffer) assert.NotNil(t, toolbox.NewJSONEncoderFactory().Create(buffer)) } func TestMarshalEncoderFactory(t *testing.T) { buffer := new(bytes.Buffer) encoder := toolbox.NewMarshalerEncoderFactory().Create(buffer) foo := &Foo200{"abc"} err := encoder.Encode(foo) assert.Nil(t, err) assert.Equal(t, "abc", string(buffer.Bytes())) err = encoder.Encode(&Foo201{}) assert.NotNil(t, err) } type Foo200 struct { Attr string } func (m *Foo200) Marshal() ([]byte, error) { return []byte(m.Attr), nil } type Foo201 struct { Attr string } toolbox-0.33.2/error.go000066400000000000000000000030321374110251100147360ustar00rootroot00000000000000package toolbox import ( "fmt" "io" "strings" ) //NilPointerError represents nil pointer error type NilPointerError struct { message string } //Error returns en error func (e *NilPointerError) Error() string { if e.message == "" { return "NilPointerError" } return e.message } //NewNilPointerError creates a new nil pointer error func NewNilPointerError(message string) error { return &NilPointerError{ message: message, } } //IsNilPointerError returns true if error is nil pointer func IsNilPointerError(err error) bool { if err == nil { return false } _, ok := err.(*NilPointerError) return ok } //IsEOFError returns true if io.EOF func IsEOFError(err error) bool { if err == nil { return false } return err == io.EOF } //NotFoundError represents not found error type NotFoundError struct { URL string } func (e *NotFoundError) Error() string { return fmt.Sprintf("not found: %v", e.URL) } //IsNotFoundError checks is supplied error is NotFoundError type func IsNotFoundError(err error) bool { if err == nil { return false } _, ok := err.(*NotFoundError) return ok } //ReclassifyNotFoundIfMatched reclassify error if not found func ReclassifyNotFoundIfMatched(err error, URL string) error { if err == nil { return nil } message := strings.ToLower(err.Error()) if strings.Contains(message, "doesn't exist") || strings.Contains(message, "no such file or directory") || strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "nosuchbucket") { return &NotFoundError{URL: URL} } return err } toolbox-0.33.2/file_logger.go000066400000000000000000000162531374110251100160740ustar00rootroot00000000000000package toolbox import ( "bytes" "errors" "fmt" "os" "os/signal" "strings" "sync" "sync/atomic" "syscall" "time" ) //FileLoggerConfig represents FileLogger type FileLoggerConfig struct { LogType string FileTemplate string filenameProvider func(t time.Time) string QueueFlashCount int MaxQueueSize int FlushRequencyInMs int //type backward-forward compatibility FlushFrequencyInMs int MaxIddleTimeInSec int inited bool } func (c *FileLoggerConfig) Init() { if c.inited { return } defaultProvider := func(t time.Time) string { return c.FileTemplate } c.inited = true template := c.FileTemplate c.filenameProvider = defaultProvider startIndex := strings.Index(template, "[") if startIndex == -1 { return } endIndex := strings.Index(template, "]") if endIndex == -1 { return } format := template[startIndex+1 : endIndex] layout := DateFormatToLayout(format) c.filenameProvider = func(t time.Time) string { formatted := t.Format(layout) return strings.Replace(template, "["+format+"]", formatted, 1) } } //Validate valides configuration sttings func (c *FileLoggerConfig) Validate() error { if len(c.LogType) == 0 { return errors.New("Log type was empty") } if c.FlushFrequencyInMs == 0 { c.FlushFrequencyInMs = c.FlushRequencyInMs } if c.FlushFrequencyInMs == 0 { return errors.New("FlushFrequencyInMs was 0") } if c.MaxQueueSize == 0 { return errors.New("MaxQueueSize was 0") } if len(c.FileTemplate) == 0 { return errors.New("FileTemplate was empty") } if c.MaxIddleTimeInSec == 0 { return errors.New("MaxIddleTimeInSec was 0") } if c.QueueFlashCount == 0 { return errors.New("QueueFlashCount was 0") } return nil } //LogStream represents individual log stream type LogStream struct { Name string Logger *FileLogger Config *FileLoggerConfig RecordCount int File *os.File LastAddQueueTime time.Time LastWriteTime uint64 Messages chan string Complete chan bool } //Log logs message into stream func (s *LogStream) Log(message *LogMessage) error { if message == nil { return errors.New("message was nil") } var textMessage = "" var ok bool if textMessage, ok = message.Message.(string); ok { } else if IsStruct(message.Message) || IsMap(message.Message) || IsSlice(message.Message) { var buf = new(bytes.Buffer) err := NewJSONEncoderFactory().Create(buf).Encode(message.Message) if err != nil { return err } textMessage = strings.Trim(buf.String(), "\n\r") } else { return fmt.Errorf("unsupported type: %T", message.Message) } s.Messages <- textMessage s.LastAddQueueTime = time.Now() return nil } func (s *LogStream) write(message string) error { atomic.StoreUint64(&s.LastWriteTime, uint64(time.Now().UnixNano())) _, err := s.File.WriteString(message) if err != nil { return err } return s.File.Sync() } //Close closes stream. func (s *LogStream) Close() { s.Logger.streamMapMutex.Lock() delete(s.Logger.streams, s.Name) s.Logger.streamMapMutex.Unlock() s.File.Close() } func (s *LogStream) isFrequencyFlushNeeded() bool { elapsedInMs := (int(time.Now().UnixNano()) - int(atomic.LoadUint64(&s.LastWriteTime))) / 1000000 return elapsedInMs >= s.Config.FlushFrequencyInMs } func (s *LogStream) manageWritesInBatch() { messageCount := 0 var message, messages string var timeout = time.Duration(2 * int(s.Config.FlushFrequencyInMs) * int(time.Millisecond)) for { select { case done := <-s.Complete: if done { manageWritesInBatchLoopFlush(s, messageCount, messages) s.Close() os.Exit(0) } case <-time.After(timeout): if !manageWritesInBatchLoopFlush(s, messageCount, messages) { return } messageCount = 0 messages = "" case message = <-s.Messages: messages += message + "\n" messageCount++ s.RecordCount++ var hasReachMaxRecrods = messageCount >= s.Config.QueueFlashCount && s.Config.QueueFlashCount > 0 if hasReachMaxRecrods || s.isFrequencyFlushNeeded() { _ = s.write(messages) messages = "" messageCount = 0 } } } } func manageWritesInBatchLoopFlush(s *LogStream, messageCount int, messages string) bool { if messageCount > 0 { if s.isFrequencyFlushNeeded() { err := s.write(messages) if err != nil { fmt.Printf("failed to write to log due to %v", err) } return true } } elapsedInMs := (int(time.Now().UnixNano()) - int(atomic.LoadUint64(&s.LastWriteTime))) / 1000000 if elapsedInMs > s.Config.MaxIddleTimeInSec*1000 { s.Close() return false } return true } //FileLogger represents a file logger type FileLogger struct { config map[string]*FileLoggerConfig streamMapMutex *sync.RWMutex streams map[string]*LogStream siginal chan os.Signal } func (l *FileLogger) getConfig(messageType string) (*FileLoggerConfig, error) { config, found := l.config[messageType] if !found { return nil, errors.New("failed to lookup config for " + messageType) } config.Init() return config, nil } //NewLogStream creat a new LogStream for passed om path and file config func (l *FileLogger) NewLogStream(path string, config *FileLoggerConfig) (*LogStream, error) { osFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { return nil, err } logStream := &LogStream{Name: path, Logger: l, Config: config, File: osFile, Messages: make(chan string, config.MaxQueueSize), Complete: make(chan bool)} go func() { logStream.manageWritesInBatch() }() return logStream, nil } func (l *FileLogger) acquireLogStream(messageType string) (*LogStream, error) { config, err := l.getConfig(messageType) if err != nil { return nil, err } fileName := config.filenameProvider(time.Now()) l.streamMapMutex.RLock() logStream, found := l.streams[fileName] l.streamMapMutex.RUnlock() if found { return logStream, nil } logStream, err = l.NewLogStream(fileName, config) if err != nil { return nil, err } l.streamMapMutex.Lock() l.streams[fileName] = logStream l.streamMapMutex.Unlock() return logStream, nil } //Log logs message into stream func (l *FileLogger) Log(message *LogMessage) error { logStream, err := l.acquireLogStream(message.MessageType) if err != nil { return err } return logStream.Log(message) } //Notify notifies logger func (l *FileLogger) Notify(siginal os.Signal) { l.siginal <- siginal } //NewFileLogger create new file logger func NewFileLogger(configs ...FileLoggerConfig) (*FileLogger, error) { result := &FileLogger{ config: make(map[string]*FileLoggerConfig), streamMapMutex: &sync.RWMutex{}, streams: make(map[string]*LogStream), } for i := range configs { err := configs[i].Validate() if err != nil { return nil, err } result.config[configs[i].LogType] = &configs[i] } // If there's a signal to quit the program send it to channel result.siginal = make(chan os.Signal, 1) signal.Notify(result.siginal, syscall.SIGINT, syscall.SIGTERM) go func() { // Block until receive a quit signal _quit := <-result.siginal _ = _quit // don't care which type for _, stream := range result.streams { // No wait flush stream.Config.FlushFrequencyInMs = 0 // Write logs now stream.Complete <- true } }() return result, nil } toolbox-0.33.2/file_logger_test.go000066400000000000000000000075421374110251100171340ustar00rootroot00000000000000package toolbox_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "io/ioutil" "os" "syscall" "testing" "time" ) func TestConfigLogger(t *testing.T) { _, err := toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyy].log", QueueFlashCount: 5, MaxQueueSize: 100, FlushRequencyInMs: 250, //MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) _, err = toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyy].log", QueueFlashCount: 5, MaxQueueSize: 100, //FlushRequencyInMs: 250, MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) _, err = toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyy].log", QueueFlashCount: 5, //MaxQueueSize :100, FlushRequencyInMs: 250, MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) _, err = toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyy].log", //QueueFlashCount :5, MaxQueueSize: 100, FlushRequencyInMs: 250, MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) _, err = toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", //FileTemplate :"/tmp/test[yyyy].log", QueueFlashCount: 5, MaxQueueSize: 100, FlushRequencyInMs: 250, MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) _, err = toolbox.NewFileLogger(toolbox.FileLoggerConfig{ //LogType :"test", FileTemplate: "/tmp/test[yyyy].log", QueueFlashCount: 5, MaxQueueSize: 100, FlushRequencyInMs: 250, MaxIddleTimeInSec: 2, }) assert.NotNil(t, err) } func TestLogger(t *testing.T) { testFile := fmt.Sprintf("/tmp/test%v.log", time.Now().Year()) toolbox.RemoveFileIfExist(testFile) defer toolbox.RemoveFileIfExist(testFile) logger, err := toolbox.NewFileLogger(toolbox.FileLoggerConfig{ LogType: "test", FileTemplate: "/tmp/test[yyyy].log", QueueFlashCount: 4, MaxQueueSize: 100, FlushRequencyInMs: 600, MaxIddleTimeInSec: 1, }) assert.Nil(t, err) for i := 0; i < 6; i++ { logger.Log(&toolbox.LogMessage{ MessageType: "test", Message: fmt.Sprintf("Abc%v", i), }) time.Sleep(10 * time.Millisecond) } time.Sleep(400 * time.Millisecond) if file, err := os.Open(testFile); err == nil { defer file.Close() content, err := ioutil.ReadAll(file) assert.Nil(t, err) assert.Equal(t, "Abc0\nAbc1\nAbc2\nAbc3\nAbc4\n", string(content)) } time.Sleep(1 * time.Second) if file, err := os.Open(testFile); err == nil { defer file.Close() content, err := ioutil.ReadAll(file) assert.Nil(t, err) assert.Equal(t, "Abc0\nAbc1\nAbc2\nAbc3\nAbc4\nAbc5\n", string(content)) } time.Sleep(1 * time.Second) if file, err := os.Open(testFile); err == nil { file.Close() os.Remove(testFile) } logger.Notify(syscall.SIGKILL) } // //func TestFileLogger_Notify(t *testing.T) { // // testFile := fmt.Sprintf("/tmp/test%v.log", time.Now().Year()) // toolbox.RemoveFileIfExist(testFile) // defer toolbox.RemoveFileIfExist(testFile) // // logger, err := toolbox.NewFileLogger(toolbox.FileLoggerConfig{ // LogType: "test", // FileTemplate: "/tmp/test[yyyy].log", // QueueFlashCount: 40, // MaxQueueSize: 100, // FlushRequencyInMs: 1200000, // MaxIddleTimeInSec: 20, // }) // // assert.Nil(t, err) // // for i := 0; i < 6; i++ { // logger.Log(&toolbox.LogMessage{ // MessageType: "test", // Message: fmt.Sprintf("Abc%v", i), // }) // } // logger.Notify(syscall.SIGKILL) // time.Sleep(100 * time.Millisecond) // if file, err := os.Open(testFile); err == nil { // defer file.Close() // content, err := ioutil.ReadAll(file) // assert.Nil(t, err) // assert.Equal(t, "Abc0\nAbc1\nAbc2\nAbc3\nAbc4\nAbc5\n", string(content)) // } // assert.Nil(t, err) //} toolbox-0.33.2/fileset_info.go000066400000000000000000000335301374110251100162610ustar00rootroot00000000000000package toolbox import ( "fmt" "go/ast" "go/parser" "go/token" "go/types" "io/ioutil" "path" "path/filepath" "strings" ) //FieldInfo represents a filed info type FieldInfo struct { Name string TypeName string ComponentType string IsPointerComponent bool KeyTypeName string ValueTypeName string TypePackage string IsAnonymous bool IsMap bool IsChannel bool IsSlice bool IsPointer bool Tag string Comment string IsVariant bool } //NewFunctionInfoFromField creates a new function info. func NewFunctionInfoFromField(field *ast.Field, owner *FileInfo) *FunctionInfo { result := &FunctionInfo{ Name: "", ParameterFields: make([]*FieldInfo, 0), ResultsFields: make([]*FieldInfo, 0), } if len(field.Names) > 0 { result.Name = field.Names[0].Name } if funcType, ok := field.Type.(*ast.FuncType); ok { if funcType.Params != nil && len(funcType.Params.List) > 0 { result.ParameterFields = toFieldInfoSlice(funcType.Params) } if funcType.Results != nil && len(funcType.Results.List) > 0 { result.ResultsFields = toFieldInfoSlice(funcType.Results) } var names = make(map[string]bool) for _, param := range result.ParameterFields { if strings.Contains(strings.ToLower(param.TypeName), strings.ToLower(param.Name)) { name := matchLastNameSegment(param.TypeName) if _, has := names[name]; has { continue } names[name] = true param.Name = name } } } return result } func matchLastNameSegment(name string) string { var result = make([]byte, 0) for i := len(name) - 1; i >= 0; i-- { aChar := string(name[i : i+1]) if aChar != "." { result = append(result, byte(aChar[0])) } if strings.ToUpper(aChar) == aChar || aChar == "." { ReverseSlice(result) return string(result) } } return name } //NewFieldInfo creates a new field info. func NewFieldInfo(field *ast.Field) *FieldInfo { return NewFieldInfoByIndex(field, 0) } //NewFieldInfoByIndex creates a new field info. func NewFieldInfoByIndex(field *ast.Field, index int) *FieldInfo { result := &FieldInfo{ Name: "", TypeName: types.ExprString(field.Type), } if len(field.Names) > 0 { result.Name = field.Names[index].Name } else { result.Name = strings.Replace(strings.Replace(result.TypeName, "[]", "", len(result.TypeName)), "*", "", len(result.TypeName)) result.IsAnonymous = true } _, result.IsMap = field.Type.(*ast.MapType) var arrayType *ast.ArrayType if arrayType, result.IsSlice = field.Type.(*ast.ArrayType); result.IsSlice { switch x := arrayType.Elt.(type) { case *ast.Ident: result.ComponentType = x.Name case *ast.StarExpr: switch y := x.X.(type) { case *ast.Ident: result.ComponentType = y.Name case *ast.SelectorExpr: result.ComponentType = y.X.(*ast.Ident).Name + "." + y.Sel.Name } result.IsPointerComponent = true case *ast.SelectorExpr: result.ComponentType = x.X.(*ast.Ident).Name + "." + x.Sel.Name } } _, result.IsPointer = field.Type.(*ast.StarExpr) _, result.IsChannel = field.Type.(*ast.ChanType) if selector, ok := field.Type.(*ast.SelectorExpr); ok { result.TypePackage = types.ExprString(selector.X) } if result.IsPointer { if pointerExpr, casted := field.Type.(*ast.StarExpr); casted { if identExpr, ok := pointerExpr.X.(*ast.Ident); ok { result.TypeName = identExpr.Name } } } else if identExpr, ok := field.Type.(*ast.Ident); ok { result.TypeName = identExpr.Name } if field.Tag != nil { result.Tag = field.Tag.Value } if mapType, ok := field.Type.(*ast.MapType); ok { result.KeyTypeName = types.ExprString(mapType.Key) result.ValueTypeName = types.ExprString(mapType.Value) } if strings.Contains(result.TypeName, "...") { result.IsVariant = true result.TypeName = strings.Replace(result.TypeName, "...", "[]", 1) } if index := strings.Index(result.TypeName, "."); index != -1 { from := 0 if result.IsPointer { from = 1 } result.TypePackage = string(result.TypeName[from:index]) } return result } //FunctionInfo represents a function info type FunctionInfo struct { Name string ReceiverTypeName string ParameterFields []*FieldInfo ResultsFields []*FieldInfo *FileInfo } //NewFunctionInfo create a new function func NewFunctionInfo(funcDeclaration *ast.FuncDecl, owner *FileInfo) *FunctionInfo { result := &FunctionInfo{ Name: "", ParameterFields: make([]*FieldInfo, 0), ResultsFields: make([]*FieldInfo, 0), } if funcDeclaration.Name != nil { result.Name = funcDeclaration.Name.Name } if funcDeclaration.Recv != nil { receiverType := funcDeclaration.Recv.List[0].Type if ident, ok := receiverType.(*ast.Ident); ok { result.ReceiverTypeName = ident.Name } else if startExpr, ok := receiverType.(*ast.StarExpr); ok { if ident, ok := startExpr.X.(*ast.Ident); ok { result.ReceiverTypeName = ident.Name } } } return result } //TypeInfo represents a struct info type TypeInfo struct { Name string Package string FileName string Comment string IsSlice bool IsStruct bool IsInterface bool IsDerived bool ComponentType string IsPointerComponentType bool Derived string Settings map[string]string fields []*FieldInfo indexedField map[string]*FieldInfo receivers []*FunctionInfo indexedReceiver map[string]*FunctionInfo rcv *FunctionInfo } //AddFields appends fileds to structinfo func (s *TypeInfo) AddFields(fields ...*FieldInfo) { s.fields = append(s.fields, fields...) for _, field := range fields { s.indexedField[field.Name] = field } } //Field returns filedinfo for supplied file name func (s *TypeInfo) Field(name string) *FieldInfo { return s.indexedField[name] } //Fields returns all fields func (s *TypeInfo) Fields() []*FieldInfo { return s.fields } //HasField returns true if struct has passed in field. func (s *TypeInfo) HasField(name string) bool { _, found := s.indexedField[name] return found } //Receivers returns struct functions func (s *TypeInfo) Receivers() []*FunctionInfo { return s.receivers } //Receiver returns receiver for passed in name func (s *TypeInfo) Receiver(name string) *FunctionInfo { return s.indexedReceiver[name] } //HasReceiver returns true if receiver is defined for struct func (s *TypeInfo) HasReceiver(name string) bool { _, found := s.indexedReceiver[name] return found } //AddReceivers adds receiver for the struct func (s *TypeInfo) AddReceivers(receivers ...*FunctionInfo) { s.receivers = append(s.receivers, receivers...) for _, receiver := range receivers { s.indexedReceiver[receiver.Name] = receiver } } //NewTypeInfo creates a new struct info func NewTypeInfo(name string) *TypeInfo { return &TypeInfo{Name: name, fields: make([]*FieldInfo, 0), receivers: make([]*FunctionInfo, 0), indexedReceiver: make(map[string]*FunctionInfo), indexedField: make(map[string]*FieldInfo), Settings: make(map[string]string)} } //FileInfo represent hold definition about all defined types and its receivers in a file type FileInfo struct { basePath string filename string types map[string]*TypeInfo functions map[string][]*FunctionInfo packageName string currentTypInfo *TypeInfo fileSet *token.FileSet currentFunctionInfo *FunctionInfo Imports map[string]string } //Type returns a type info for passed in name func (f *FileInfo) Type(name string) *TypeInfo { return f.types[name] } //Type returns a struct info for passed in name func (f *FileInfo) addFunction(funcion *FunctionInfo) { functions, found := f.functions[funcion.ReceiverTypeName] if !found { functions = make([]*FunctionInfo, 0) f.functions[funcion.ReceiverTypeName] = functions } f.functions[funcion.ReceiverTypeName] = append(f.functions[funcion.ReceiverTypeName], funcion) } //Types returns all struct info func (f *FileInfo) Types() []*TypeInfo { var result = make([]*TypeInfo, 0) for _, v := range f.types { result = append(result, v) } return result } //HasType returns truc if struct info is defined in a file func (f *FileInfo) HasType(name string) bool { _, found := f.types[name] return found } //readComment reads comment from the position func (f *FileInfo) readComment(pos token.Pos) string { position := f.fileSet.Position(pos) fileName := path.Join(f.basePath, f.filename) content, err := ioutil.ReadFile(fileName) if err != nil { panic("Unable to open file " + fileName) } line := strings.Split(string(content), "\n")[position.Line-1] commentPosition := strings.LastIndex(line, "//") if commentPosition != -1 { return line[commentPosition+2:] } return "" } //toFieldInfoSlice converts filedList to FiledInfo slice. func toFieldInfoSlice(source *ast.FieldList) []*FieldInfo { var result = make([]*FieldInfo, 0) if source == nil || len(source.List) == 0 { return result } for _, field := range source.List { if len(field.Names) > 0 { for i := range field.Names { result = append(result, NewFieldInfoByIndex(field, i)) } } else { result = append(result, NewFieldInfoByIndex(field, 0)) } } return result } //toFunctionInfos convers filedList to function info slice. func toFunctionInfos(source *ast.FieldList, owner *FileInfo) []*FunctionInfo { var result = make([]*FunctionInfo, 0) if source == nil || len(source.List) == 0 { return result } for _, field := range source.List { result = append(result, NewFunctionInfoFromField(field, owner)) } return result } //Visit visits ast node to extract struct details from the passed file func (f *FileInfo) Visit(node ast.Node) ast.Visitor { if node != nil { // fmt.Printf("node %T %f\n", node, node) switch value := node.(type) { case *ast.TypeSpec: typeName := value.Name.Name typeInfo := NewTypeInfo(typeName) typeInfo.Package = f.packageName typeInfo.FileName = f.filename switch typeValue := value.Type.(type) { case *ast.ArrayType: typeInfo.IsSlice = true if ident, ok := typeValue.Elt.(*ast.Ident); ok { typeInfo.ComponentType = ident.Name } else if startExpr, ok := typeValue.Elt.(*ast.StarExpr); ok { if ident, ok := startExpr.X.(*ast.Ident); ok { typeInfo.ComponentType = ident.Name } typeInfo.IsPointerComponentType = true } case *ast.StructType: typeInfo.IsStruct = true case *ast.InterfaceType: typeInfo.IsInterface = true case *ast.Ident: typeInfo.Derived = typeValue.Name typeInfo.IsDerived = true } f.currentTypInfo = typeInfo f.types[typeName] = typeInfo case *ast.StructType: if f.currentTypInfo != nil { //TODO fixme - understand why current type would be nil f.currentTypInfo.Comment = f.readComment(value.Pos()) f.currentTypInfo.AddFields(toFieldInfoSlice(value.Fields)...) } case *ast.FuncDecl: functionInfo := NewFunctionInfo(value, f) functionInfo.FileInfo = f f.currentFunctionInfo = functionInfo if len(functionInfo.ReceiverTypeName) > 0 { f.addFunction(functionInfo) } case *ast.FuncType: if f.currentFunctionInfo != nil { if value.Params != nil { f.currentFunctionInfo.ParameterFields = toFieldInfoSlice(value.Params) } if value.Results != nil { f.currentFunctionInfo.ResultsFields = toFieldInfoSlice(value.Results) } f.currentFunctionInfo = nil } case *ast.FieldList: if f.currentTypInfo != nil && f.currentTypInfo.IsInterface { f.currentTypInfo.receivers = toFunctionInfos(value, f) f.currentTypInfo = nil } case *ast.ImportSpec: if value.Name != nil && value.Name.String() != "" { f.Imports[value.Name.String()] = value.Path.Value } else { _, name := path.Split(value.Path.Value) name = strings.Replace(name, `"`, "", 2) f.Imports[name] = value.Path.Value } } } return f } //NewFileInfo creates a new file info. func NewFileInfo(basePath, packageName, filename string, fileSet *token.FileSet) *FileInfo { result := &FileInfo{ basePath: basePath, filename: filename, packageName: packageName, types: make(map[string]*TypeInfo), functions: make(map[string][]*FunctionInfo), Imports: make(map[string]string), fileSet: fileSet} return result } //FileSetInfo represents a fileset info storing information about go file with their struct definition type FileSetInfo struct { files map[string]*FileInfo } //FileInfo returns fileinfo for supplied file name func (f *FileSetInfo) FileInfo(name string) *FileInfo { return f.files[name] } //FilesInfo returns all files info. func (f *FileSetInfo) FilesInfo() map[string]*FileInfo { return f.files } //Type returns type info for passed in type name. func (f *FileSetInfo) Type(name string) *TypeInfo { if pointerIndex := strings.LastIndex(name, "*"); pointerIndex != -1 { name = name[pointerIndex+1:] } for _, v := range f.files { if v.HasType(name) { return v.Type(name) } } return nil } //NewFileSetInfo creates a new fileset info func NewFileSetInfo(baseDir string) (*FileSetInfo, error) { fileSet := token.NewFileSet() pkgs, err := parser.ParseDir(fileSet, baseDir, nil, parser.ParseComments) if err != nil { return nil, fmt.Errorf("failed to parse path %v: %v", baseDir, err) } var result = &FileSetInfo{ files: make(map[string]*FileInfo), } for packageName, pkg := range pkgs { for filename, file := range pkg.Files { filename := filepath.Base(filename) fileInfo := NewFileInfo(baseDir, packageName, filename, fileSet) ast.Walk(fileInfo, file) result.files[filename] = fileInfo } } for _, fileInfo := range result.files { for k, functionsInfo := range fileInfo.functions { typeInfo := result.Type(k) if typeInfo != nil && typeInfo.IsStruct { typeInfo.AddReceivers(functionsInfo...) } } } return result, nil } toolbox-0.33.2/fileset_info_test.go000066400000000000000000000045651374110251100173260ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestNewFileSetInfoInfo(t *testing.T) { if (32 << uintptr(^uintptr(0)>>63)) < 64 { t.Skip() } fileSetInfo, err := toolbox.NewFileSetInfo("./test/fileset_info/") if err != nil { panic(err) } assert.True(t, len(fileSetInfo.FilesInfo()) > 0) fileInfo := fileSetInfo.FileInfo("user.go") assert.NotNil(t, fileInfo) addresses := fileSetInfo.Type("Addresses") assert.NotNil(t, addresses) assert.False(t, fileInfo.HasType("F")) assert.True(t, fileInfo.HasType("User")) assert.Equal(t, 7, len(fileInfo.Types())) address := fileSetInfo.Type("Address") assert.NotNil(t, address) assert.Equal(t, 2, len(address.Fields())) country := address.Field("Country") assert.NotNil(t, country) assert.True(t, country.IsAnonymous) z := fileSetInfo.Type("Z") assert.NotNil(t, z) address2 := fileSetInfo.Type("Address2") assert.Nil(t, address2) userInfo := fileInfo.Type("User") assert.NotNil(t, userInfo) assert.True(t, userInfo.HasField("ID")) assert.True(t, userInfo.HasField("Name")) assert.False(t, userInfo.HasField("FF")) assert.Equal(t, 11, len(userInfo.Fields())) idInfo := userInfo.Field("ID") assert.True(t, idInfo.IsPointer) assert.Equal(t, "int", idInfo.TypeName) assert.Equal(t, true, idInfo.IsPointer) dobInfo := userInfo.Field("DateOfBirth") assert.Equal(t, "time.Time", dobInfo.TypeName) assert.Equal(t, "time", dobInfo.TypePackage) assert.Equal(t, "`foo=\"bar\"`", dobInfo.Tag) addressPointer := userInfo.Field("AddressPointer") assert.NotNil(t, addressPointer) assert.Equal(t, "Address", addressPointer.TypeName) cInfo := userInfo.Field("C") assert.True(t, cInfo.IsChannel) mInfo := userInfo.Field("M") assert.True(t, mInfo.IsMap) assert.Equal(t, "string", mInfo.KeyTypeName) assert.Equal(t, "[]string", mInfo.ValueTypeName) intsInfo := userInfo.Field("Ints") assert.True(t, intsInfo.IsSlice) assert.Equal(t, "my comments", userInfo.Comment) assert.False(t, userInfo.HasReceiver("Abc")) assert.True(t, len(userInfo.Receivers()) > 1) assert.True(t, userInfo.HasReceiver("Test")) assert.True(t, userInfo.HasReceiver("Test2")) receiver := userInfo.Receiver("Test") assert.NotNil(t, receiver) appointments := userInfo.Field("Appointments") assert.NotNil(t, appointments) assert.Equal(t, "time.Time", appointments.ComponentType) } toolbox-0.33.2/function_util.go000066400000000000000000000120471374110251100164750ustar00rootroot00000000000000package toolbox import ( "fmt" "reflect" "strings" ) //GetFunction returns function for provided owner and name, or error func GetFunction(owner interface{}, name string) (interface{}, error) { if owner == nil { return nil, fmt.Errorf("failed to lookup %v on %T, owner was nil", name, owner) } var ownerType = reflect.TypeOf(owner) var method, has = ownerType.MethodByName(name) if !has { var available = make([]string, 0) for i := 0; i < ownerType.NumMethod(); i++ { available = append(available, ownerType.Method(i).Name) } return nil, fmt.Errorf("failed to lookup %T.%v, available:[%v]", owner, name, strings.Join(available, ",")) } return reflect.ValueOf(owner).MethodByName(method.Name).Interface(), nil } //CallFunction calls passed in function with provided parameters,it returns a function result. func CallFunction(function interface{}, parameters ...interface{}) []interface{} { AssertKind(function, reflect.Func, "function") var functionParameters = make([]reflect.Value, 0) ProcessSlice(parameters, func(item interface{}) bool { functionParameters = append(functionParameters, reflect.ValueOf(item)) return true }) functionValue := reflect.ValueOf(function) var resultValues = functionValue.Call(functionParameters) var result = make([]interface{}, len(resultValues)) for i, resultValue := range resultValues { result[i] = resultValue.Interface() } return result } //AsCompatibleFunctionParameters takes incompatible function parameters and converts then into provided function signature compatible func AsCompatibleFunctionParameters(function interface{}, parameters []interface{}) ([]interface{}, error) { return AsFunctionParameters(function, parameters, map[string]interface{}{}) } //AsFunctionParameters takes incompatible function parameters and converts then into provided function signature compatible func AsFunctionParameters(function interface{}, parameters []interface{}, parametersKV map[string]interface{}) ([]interface{}, error) { AssertKind(function, reflect.Func, "function") functionValue := reflect.ValueOf(function) funcSignature := GetFuncSignature(function) actualMethodSignatureLength := len(funcSignature) converter := Converter{} if actualMethodSignatureLength != len(parameters) { return nil, fmt.Errorf("invalid number of parameters wanted: [%T], had: %v", function, len(parameters)) } var functionParameters = make([]interface{}, 0) for i, parameterValue := range parameters { isStruct := IsStruct(funcSignature[i]) if isStruct && parameterValue == nil { parameterValue = make(map[string]interface{}) } reflectValue := reflect.ValueOf(parameterValue) if !isStruct { if parameterValue == nil { return nil, fmt.Errorf("parameter[%v] was empty", i) } if reflectValue.Kind() == reflect.Slice && funcSignature[i].Kind() != reflectValue.Kind() { return nil, fmt.Errorf("incompatible types expected: %v, but had %v", funcSignature[i].Kind(), reflectValue.Kind()) } else if !reflectValue.IsValid() { if funcSignature[i].Kind() == reflect.Slice { parameterValue = reflect.New(funcSignature[i]).Interface() reflectValue = reflect.ValueOf(parameterValue) } } } if reflectValue.Type() != funcSignature[i] { newValuePointer := reflect.New(funcSignature[i]) var err error if IsStruct(funcSignature[i]) && !(IsStruct(parameterValue) || IsMap(parameterValue)) { err = converter.AssignConverted(newValuePointer.Interface(), parametersKV) } else { err = converter.AssignConverted(newValuePointer.Interface(), parameterValue) } if err != nil { return nil, fmt.Errorf("failed to assign convert %v to %v due to %v", parametersKV, newValuePointer.Interface(), err) } reflectValue = newValuePointer.Elem() } if functionValue.Type().IsVariadic() && funcSignature[i].Kind() == reflect.Slice && i+1 == len(funcSignature) { ProcessSlice(reflectValue.Interface(), func(item interface{}) bool { functionParameters = append(functionParameters, item) return true }) } else { functionParameters = append(functionParameters, reflectValue.Interface()) } } return functionParameters, nil } //BuildFunctionParameters builds function parameters provided in the parameterValues. // Parameters value will be converted if needed to expected by the function signature type. It returns function parameters , or error func BuildFunctionParameters(function interface{}, parameters []string, parameterValues map[string]interface{}) ([]interface{}, error) { var functionParameters = make([]interface{}, 0) for _, name := range parameters { functionParameters = append(functionParameters, parameterValues[name]) } return AsFunctionParameters(function, functionParameters, parameterValues) } //GetFuncSignature returns a function signature func GetFuncSignature(function interface{}) []reflect.Type { AssertKind(function, reflect.Func, "function") functionValue := reflect.ValueOf(function) var result = make([]reflect.Type, 0) functionType := functionValue.Type() for i := 0; i < functionType.NumIn(); i++ { result = append(result, functionType.In(i)) } return result } toolbox-0.33.2/function_util_test.go000066400000000000000000000034361374110251100175360ustar00rootroot00000000000000package toolbox_test import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestCallFunction(t *testing.T) { { var myFunction = func(arg1 string, arg2 int) string { return fmt.Sprintf("%v%v", arg2, arg1) } functionParameters, err := toolbox.BuildFunctionParameters(myFunction, []string{"arg1", "arg2"}, map[string]interface{}{ "arg1": "abc", "arg2": 100, }) assert.Nil(t, err) result := toolbox.CallFunction(myFunction, functionParameters...) assert.Equal(t, "100abc", result[0]) } { var myFunction = func(arg1 string, arg2 ...int) string { return fmt.Sprintf("%v%v", arg2, arg1) } functionParameters, err := toolbox.BuildFunctionParameters(myFunction, []string{"arg1", "arg2"}, map[string]interface{}{ "arg1": "abc", "arg2": []interface{}{100}, }) assert.Nil(t, err) result := toolbox.CallFunction(myFunction, functionParameters...) assert.Equal(t, "[100]abc", result[0]) } { var myFunction = func(arg1 string, arg2 ...int) string { return fmt.Sprintf("%v%v", arg2, arg1) } _, err := toolbox.BuildFunctionParameters(myFunction, []string{"arg1", "arg2"}, map[string]interface{}{ "arg1": "abc", "arg2": 100, }) assert.NotNil(t, err) } } func Test_GetFunction(t *testing.T) { var astruct = &AStruct{"ABC"} var function, err = toolbox.GetFunction(astruct, "Message") assert.Nil(t, err) assert.NotNil(t, function) parameters, err := toolbox.AsCompatibleFunctionParameters(function, []interface{}{"aaa"}) assert.Nil(t, err) result := toolbox.CallFunction(function, parameters...) assert.Equal(t, 2, len(result)) assert.Equal(t, "ABC.aaa", result[0]) } type AStruct struct { A string } func (s *AStruct) Message(a string) (string, error) { return fmt.Sprintf("%v.%v", s.A, a), nil } toolbox-0.33.2/iterator.go000066400000000000000000000045441374110251100154470ustar00rootroot00000000000000package toolbox import ( "reflect" "time" ) //Iterator represents generic iterator. type Iterator interface { //HasNext returns true if iterator has next element. HasNext() bool //Next sets item pointer with next element. Next(itemPointer interface{}) error } type sliceIterator struct { sliceValue reflect.Value index int } func (i *sliceIterator) HasNext() bool { return i.index < i.sliceValue.Len() } func (i *sliceIterator) Next(itemPointer interface{}) error { value := i.sliceValue.Index(i.index) i.index++ itemPointerValue := reflect.ValueOf(itemPointer) itemPointerValue.Elem().Set(value) return nil } type stringSliceIterator struct { sliceValue []string index int } func (i *stringSliceIterator) HasNext() bool { return i.index < len(i.sliceValue) } func (i *stringSliceIterator) Next(itemPointer interface{}) error { value := i.sliceValue[i.index] i.index++ if stringPointer, ok := itemPointer.(*string); ok { *stringPointer = value return nil } interfacePointer := itemPointer.(*interface{}) *interfacePointer = value return nil } type interfaceSliceIterator struct { sliceValue []interface{} index int } func (i *interfaceSliceIterator) HasNext() bool { return i.index < len(i.sliceValue) } func (i *interfaceSliceIterator) Next(itemPointer interface{}) error { value := i.sliceValue[i.index] i.index++ switch actual := itemPointer.(type) { case *interface{}: *actual = value return nil case *string: *actual = AsString(value) return nil case *int: *actual = AsInt(value) return nil case *int64: *actual = int64(AsInt(value)) return nil case *time.Time: var timestamp = AsTime(value, DefaultDateLayout) if timestamp != nil { *actual = *timestamp } return nil } itemPointerValue := reflect.ValueOf(itemPointer) if value != nil { itemPointerValue.Elem().Set(reflect.ValueOf(value)) } else { itemPointerValue.Elem().Set(reflect.Zero(reflect.TypeOf(itemPointer).Elem())) } return nil } //NewSliceIterator creates a new slice iterator. func NewSliceIterator(slice interface{}) Iterator { if aSlice, ok := slice.([]interface{}); ok { return &interfaceSliceIterator{aSlice, 0} } if aSlice, ok := slice.([]string); ok { return &stringSliceIterator{aSlice, 0} } sliceValue := DiscoverValueByKind(reflect.ValueOf(slice), reflect.Slice) return &sliceIterator{sliceValue: sliceValue} } toolbox-0.33.2/iterator_test.go000066400000000000000000000040341374110251100165000ustar00rootroot00000000000000package toolbox_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestSliceIterator(t *testing.T) { { slice := []string{"a", "r", "c"} iterator := toolbox.NewSliceIterator(slice) var values = make([]interface{}, 1) value := values[0] assert.True(t, iterator.HasNext()) err := iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "a", value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "r", value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "c", value) } { slice := []string{"a", "r", "c"} iterator := toolbox.NewSliceIterator(slice) value := "" assert.True(t, iterator.HasNext()) err := iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "a", value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "r", value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "c", value) } { slice := []interface{}{"a", "z", "c"} iterator := toolbox.NewSliceIterator(slice) value := "" assert.True(t, iterator.HasNext()) err := iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "a", value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, "z", value) var values = make([]interface{}, 1) assert.True(t, iterator.HasNext()) err = iterator.Next(&values[0]) assert.Nil(t, err) assert.Equal(t, "c", values[0]) } { slice := []int{3, 2, 1} iterator := toolbox.NewSliceIterator(slice) value := 0 assert.True(t, iterator.HasNext()) err := iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, 3, value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, 2, value) assert.True(t, iterator.HasNext()) err = iterator.Next(&value) assert.Nil(t, err) assert.Equal(t, 1, value) } } toolbox-0.33.2/json.go000066400000000000000000000116101374110251100145570ustar00rootroot00000000000000package toolbox import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "strings" ) //IsStructuredJSON returns true if supplied represent JSON structure (map,array) func IsStructuredJSON(candidate string) bool { candidate = strings.Trim(candidate, "\n \t\r") if candidate == "" { return false } curlyStart := strings.Count(candidate, "{") curlyEnd := strings.Count(candidate, "}") squareStart := strings.Count(candidate, "[") squareEnd := strings.Count(candidate, "]") if !(curlyStart == curlyEnd && squareStart == squareEnd) || (curlyStart+squareStart == 0) { return false } if !(strings.HasPrefix(candidate, "{") && strings.HasSuffix(candidate, "}") || strings.HasPrefix(candidate, "[") && strings.HasSuffix(candidate, "]")) { return false } return json.Valid([]byte(candidate)) } //IsCompleteJSON returns true if supplied represent complete JSON func IsCompleteJSON(candidate string) bool { return json.Valid([]byte(candidate)) } //NewLineDelimitedJSON returns JSON for supplied multi line JSON func NewLineDelimitedJSON(candidate string) ([]interface{}, error) { var result = make([]interface{}, 0) lines := getMultilineContent(candidate) for _, line := range lines { aStruct, err := JSONToInterface(line) if err != nil { return nil, err } result = append(result, aStruct) } return result, nil } func getMultilineContent(multiLineText string) []string { multiLineText = strings.TrimSpace(multiLineText) if multiLineText == "" { return []string{} } lines := strings.Split(multiLineText, "\n") var result = make([]string, 0) for _, line := range lines { if strings.Trim(line, " \r") == "" { continue } result = append(result, line) } return result } //IsNewLineDelimitedJSON returns true if supplied content is multi line delimited JSON func IsNewLineDelimitedJSON(candidate string) bool { lines := getMultilineContent(candidate) if len(lines) <= 1 { return false } return IsStructuredJSON(lines[0]) && IsStructuredJSON(lines[1]) } //JSONToInterface converts JSON source to an interface (either map or slice) func JSONToInterface(source interface{}) (interface{}, error) { var reader io.Reader switch value := source.(type) { case io.Reader: reader = value case []byte: reader = bytes.NewReader(value) case string: reader = strings.NewReader(value) default: return nil, fmt.Errorf("unsupported type: %T", source) } var result interface{} if content, err := ioutil.ReadAll(reader); err == nil { text := string(content) if IsNewLineDelimitedJSON(text) { return NewLineDelimitedJSON(text) } reader = strings.NewReader(text) } err := jsonDecoderFactory{}.Create(reader).Decode(&result) return result, err } //JSONToMap converts JSON source into map func JSONToMap(source interface{}) (map[string]interface{}, error) { var reader io.Reader switch value := source.(type) { case io.Reader: reader = value case []byte: reader = bytes.NewReader(value) case string: reader = strings.NewReader(value) default: return nil, fmt.Errorf("unsupported type: %T", source) } var result = make(map[string]interface{}) err := jsonDecoderFactory{}.Create(reader).Decode(&result) return result, err } //JSONToSlice converts JSON source into slice func JSONToSlice(source interface{}) ([]interface{}, error) { var reader io.Reader switch value := source.(type) { case io.Reader: reader = value case []byte: reader = bytes.NewReader(value) case string: reader = strings.NewReader(value) default: return nil, fmt.Errorf("unsupported type: %T", source) } var result = make([]interface{}, 0) err := jsonDecoderFactory{}.Create(reader).Decode(&result) return result, err } //AsJSONText converts data structure int text JSON func AsJSONText(source interface{}) (string, error) { if source == nil { return "", fmt.Errorf("source was nil") } if IsStruct(source) || IsMap(source) || IsSlice(source) { buf := new(bytes.Buffer) err := NewJSONEncoderFactory().Create(buf).Encode(source) return buf.String(), err } return "", fmt.Errorf("unsupported type: %T", source) } //AsIndentJSONText converts data structure int text JSON func AsIndentJSONText(source interface{}) (string, error) { if IsStruct(source) || IsMap(source) || IsSlice(source) { buf, err := json.MarshalIndent(source, "", "\t") if err != nil { return "", err } return string(buf), nil } return "", fmt.Errorf("unsupported type: %T", source) } //AnyJSONType represents any JSON type type AnyJSONType string //UnmarshalJSON implements unmarshalerinterface func (s *AnyJSONType) UnmarshalJSON(b []byte) error { *s = AnyJSONType(b) return nil } //MarshalJSON implements marshaler interface func (s *AnyJSONType) MarshalJSON() ([]byte, error) { if len(*s) == 0 { return []byte(`""`), nil } return []byte(*s), nil } //Value returns string or string slice value func (s AnyJSONType) Value() (interface{}, error) { var result interface{} return result, json.Unmarshal([]byte(s), &result) } toolbox-0.33.2/json_test.go000066400000000000000000000122471374110251100156250ustar00rootroot00000000000000package toolbox_test import ( "encoding/json" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func Test_IsCompleteJSON(t *testing.T) { { input := `{"a":1, "b":2}` assert.True(t, toolbox.IsCompleteJSON(input)) } { input := `{"a":1, "b":2} {"a2":2, "b3":21} {"a3":3, "b4:22 ` assert.False(t, toolbox.IsCompleteJSON(input)) } { input := `{"name":"abc"},{"id":"10}"` assert.False(t, toolbox.IsCompleteJSON(input)) } { input := `"abc"` assert.True(t, toolbox.IsCompleteJSON(input)) } } func Test_IsStructuredJSON(t *testing.T) { { input := `{"a":1, "b":2}` assert.True(t, toolbox.IsStructuredJSON(input)) } { input := `{"a":1, "b":2} {"a2":2, "b3":21} {"a3":3, "b4:22 ` assert.False(t, toolbox.IsStructuredJSON(input)) } { input := `{"name":"abc"},{"id":"10}"` assert.False(t, toolbox.IsStructuredJSON(input)) } { input := `"abc""` assert.False(t, toolbox.IsStructuredJSON(input)) } } func Test_IsNewDelimitedJSON(t *testing.T) { { input := `{"a":1, "b":2}` assert.False(t, toolbox.IsNewLineDelimitedJSON(input)) } { input := `{"a":1, "b":2} {"a2":2, "b3":21} {"a3":3, "b4:22} ` assert.True(t, toolbox.IsNewLineDelimitedJSON(input)) } { input := `{"a":1, "b":2} {"a2":2, "b3":21 {"a3":3, "b4:22} ` assert.False(t, toolbox.IsNewLineDelimitedJSON(input)) } { input := "{\"category\":\"Food\",\"country\":\"Poland\",\"expenditure\":\"6759.00\",\"id\":1,\"sub_category\":null,\"year\":2014}\n{\"category\":\"Housing\",\"country\":\"US\",\"expenditure\":\"17798.00\",\"id\":4,\"sub_category\":null,\"year\":2014}\n{\"category\":\"Food\",\"country\":\"Poland\",\"expenditure\":\"7023.00\",\"id\":2,\"sub_category\":null,\"year\":2015}\n{\"category\":\"Housing\",\"country\":\"US\",\"expenditure\":\"18409.00\",\"id\":5,\"sub_category\":null,\"year\":2015}\n{\"category\":\"Food\",\"country\":\"Poland\",\"expenditure\":\"7023.00\",\"id\":3,\"sub_category\":null,\"year\":2016}\n{\"category\":\"Housing\",\"country\":\"US\",\"expenditure\":\"18886.00\",\"id\":6,\"sub_category\":null,\"year\":2016}\n" assert.True(t, toolbox.IsNewLineDelimitedJSON(input)) } } func Test_JSONToMap(t *testing.T) { { input := `{"a":1, "b":2}` aMAp, err := toolbox.JSONToMap(input) assert.Nil(t, err) assert.True(t, len(aMAp) > 0) } { input := `{"a":1, "b":2}` aMAp, err := toolbox.JSONToMap([]byte(input)) assert.Nil(t, err) assert.True(t, len(aMAp) > 0) } { input := `{"a":1, "b":2}` aMAp, err := toolbox.JSONToMap(strings.NewReader(input)) assert.Nil(t, err) assert.True(t, len(aMAp) > 0) } { //error case _, err := toolbox.JSONToMap(1) assert.NotNil(t, err) } { //error case input := `{"a":1, "b":2` _, err := toolbox.JSONToMap(input) assert.NotNil(t, err) } } func Test_AsJSONText(t *testing.T) { { var soure = map[string]interface{}{ "k": 1, } text, err := toolbox.AsJSONText(soure) assert.Nil(t, err) assert.EqualValues(t, "{\"k\":1}\n", text) } { type source struct { K int } text, err := toolbox.AsJSONText(&source{K: 1}) assert.Nil(t, err) assert.EqualValues(t, "{\"K\":1}\n", text) } { text, err := toolbox.AsJSONText([]int{1, 3}) assert.Nil(t, err) assert.EqualValues(t, "[1,3]\n", text) } { _, err := toolbox.AsJSONText(1) assert.NotNil(t, err) } } func Test_JSONToInterface(t *testing.T) { { input := `{"a":1, "b":2}` output, err := toolbox.JSONToInterface(input) if assert.Nil(t, err) { assert.NotNil(t, output) assert.True(t, toolbox.IsMap(output)) aMap := toolbox.AsMap(output) assert.EqualValues(t, 1, aMap["a"]) assert.EqualValues(t, 2, aMap["b"]) } } { input := `[1,2]` output, err := toolbox.JSONToInterface(input) if assert.Nil(t, err) { assert.NotNil(t, output) assert.True(t, toolbox.IsSlice(output)) aSlice := toolbox.AsSlice(output) assert.EqualValues(t, []interface{}{1.0, 2.0}, aSlice) } } } func TestAnyJSONType_Value(t *testing.T) { var useCases = []struct { description string source string target map[string]toolbox.AnyJSONType key string expect interface{} }{ { description: "string any type", source: `{"k":"abc"}`, key: "k", expect: "abc", }, { description: "numeric any type", source: `{"k":123}`, key: "k", expect: 123, }, { description: "slice any type", source: `{"k":[1,2,3]}`, key: "k", expect: []interface{}{1.0, 2.0, 3.0}, }, { description: "slice any type", source: `{"k":{"z":[1,2]}}`, key: "k", expect: map[string]interface{}{ "z": []interface{}{float64(1), float64(2)}, }, }, } for _, useCase := range useCases { err := json.Unmarshal([]byte(useCase.source), &useCase.target) if !assert.Nil(t, err, useCase.description) { continue } actual, ok := useCase.target[useCase.key] if !assert.True(t, ok, useCase.description) { continue } actualValue, err := actual.Value() if !assert.Nil(t, err, useCase.description) { continue } assert.EqualValues(t, useCase.expect, actualValue, useCase.description) _, err = json.Marshal(useCase.target) assert.Nil(t, err) } } toolbox-0.33.2/kms/000077500000000000000000000000001374110251100140525ustar00rootroot00000000000000toolbox-0.33.2/kms/aws/000077500000000000000000000000001374110251100146445ustar00rootroot00000000000000toolbox-0.33.2/kms/aws/service.go000066400000000000000000000062741374110251100166440ustar00rootroot00000000000000package aws import ( "bytes" "context" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" akms "github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/ssm" "github.com/pkg/errors" "strings" "github.com/viant/toolbox" "github.com/viant/toolbox/kms" ) type service struct { *ssm.SSM *akms.KMS } func (s *service) Encrypt(ctx context.Context, request *kms.EncryptRequest) (*kms.EncryptResponse, error) { err := request.Validate() if err != nil { return nil, errors.Wrap(err, "invalid encrypt request") } if request.Parameter == "" { return nil, errors.New("parameter was empty") } response := &kms.EncryptResponse{} err = s.putParameters(request.Key, request.Parameter, string(request.Data)) if err == nil { parameter, err := s.getParameters(request.Parameter, false) if err != nil { return nil, err } response.EncryptedText = *parameter.Value response.EncryptedData = []byte(response.EncryptedText) } return response, err } func (s *service) Decrypt(ctx context.Context, request *kms.DecryptRequest) (*kms.DecryptResponse, error) { err := request.Validate() if err != nil { return nil, errors.Wrap(err, "invalid encrypt request") } if request.Parameter == "" { return nil, errors.New("parameter was empty") } response := &kms.DecryptResponse{} parameter, err := s.getParameters(request.Parameter, true) if err != nil { return nil, err } response.Text = *parameter.Value response.Data = []byte(response.Text) return response, nil } func (s *service) Decode(ctx context.Context, decryptRequest *kms.DecryptRequest, factory toolbox.DecoderFactory, target interface{}) error { response, err := s.Decrypt(ctx, decryptRequest) if err != nil { return err } reader := bytes.NewReader(response.Data) return factory.Create(reader).Decode(target) } func (s *service) putParameters(keyOrAlias, name, value string) error { targetKeyID, err := s.getKeyByAlias(keyOrAlias) if err != nil { return err } _, err = s.PutParameter(&ssm.PutParameterInput{ Name: aws.String(name), KeyId: &targetKeyID, Value: &value, }) return err } func (s *service) getKeyByAlias(keyOrAlias string) (string, error) { if strings.Count(keyOrAlias, ":") > 0 { return keyOrAlias, nil } var nextMarker *string for { output, err := s.ListAliases(&akms.ListAliasesInput{ Marker: nextMarker, }) if err != nil { return "", err } if len(output.Aliases) == 0 { break } for _, candidate := range output.Aliases { if *candidate.AliasName == keyOrAlias { return *candidate.TargetKeyId, nil } } nextMarker = output.NextMarker if nextMarker == nil { break } } return "", fmt.Errorf("key for alias %v no found", keyOrAlias) } func (s *service) getParameters(name string, withDecryption bool) (*ssm.Parameter, error) { output, err := s.GetParameter(&ssm.GetParameterInput{ Name: aws.String(name), WithDecryption: &withDecryption, }) if err != nil { return nil, err } return output.Parameter, nil } //New returns new kms service func New() (kms.Service, error) { sess, err := session.NewSession() if err != nil { return nil, err } return &service{ SSM: ssm.New(sess), KMS: akms.New(sess), }, nil } toolbox-0.33.2/kms/contract.go000066400000000000000000000013521374110251100162170ustar00rootroot00000000000000package kms import "errors" type Resource struct { URL string Parameter string Data []byte } type EncryptRequest struct { Key string *Resource TargetURL string } type EncryptResponse struct { EncryptedData []byte EncryptedText string } type DecryptRequest struct { Key string *Resource } type DecryptResponse struct { Data []byte Text string } func (r *EncryptRequest) Validate() error { if r.Key == "" { return errors.New("key was empty") } if r.Resource == nil { return errors.New("nothing to encrypt") } return nil } func (r *DecryptRequest) Validate() error { if r.Key == "" { return errors.New("key was empty") } if r.Resource == nil { return errors.New("nothing to decrypt") } return nil } toolbox-0.33.2/kms/gcp/000077500000000000000000000000001374110251100146235ustar00rootroot00000000000000toolbox-0.33.2/kms/gcp/service.go000066400000000000000000000120711374110251100166130ustar00rootroot00000000000000package gcp import ( "bytes" "context" "encoding/base64" "fmt" "github.com/pkg/errors" "github.com/viant/toolbox" "github.com/viant/toolbox/kms" "github.com/viant/toolbox/storage" "github.com/viant/toolbox/url" "google.golang.org/api/cloudkms/v1" "google.golang.org/api/option" "io/ioutil" ) type KmsService interface { Encrypt(ctx context.Context, key string, value string) (string, error) Decrypt(ctx context.Context, key string, value string) (string, error) } func (k *kmsService) Encrypt(ctx context.Context, key string, plainText string) (string, error) { kms, err := cloudkms.NewService(ctx, option.WithScopes(cloudkms.CloudPlatformScope, cloudkms.CloudkmsScope)) if err != nil { return "", errors.Wrap(err, fmt.Sprintf("failed to create kms server for key %v", key)) } service := cloudkms.NewProjectsLocationsKeyRingsCryptoKeysService(kms) response, err := service.Encrypt(key, &cloudkms.EncryptRequest{Plaintext: plainText}).Context(ctx).Do() if err != nil { return "", errors.Wrap(err, fmt.Sprintf("failed to encrypt with key %v", key)) } return response.Ciphertext, nil } func (k *kmsService) Decrypt(ctx context.Context, key string, plainText string) (string, error) { kms, err := cloudkms.NewService(ctx, option.WithScopes(cloudkms.CloudPlatformScope, cloudkms.CloudkmsScope)) if err != nil { return "", errors.Wrap(err, fmt.Sprintf("failed to create kms server for key %v", key)) } service := cloudkms.NewProjectsLocationsKeyRingsCryptoKeysService(kms) response, err := service.Decrypt(key, &cloudkms.DecryptRequest{Ciphertext: plainText}).Context(ctx).Do() if err != nil { return "", errors.Wrap(err, fmt.Sprintf("failed to encrypt with key %v", key)) } return response.Plaintext, nil } type kmsService struct{} type service struct { KmsService } //New returns service func New() kms.Service { return newService() } func newService() kms.Service { return &service{KmsService: &kmsService{}} } func (s *service) Decode(ctx context.Context, decryptRequest *kms.DecryptRequest, factory toolbox.DecoderFactory, target interface{}) error { response, err := s.Decrypt(ctx, decryptRequest) if err != nil { return err } reader := bytes.NewReader(response.Data) return factory.Create(reader).Decode(target) } func (s *service) Encrypt(ctx context.Context, request *kms.EncryptRequest) (*kms.EncryptResponse, error) { if request.URL != "" { data, err := getDataFromURL(request.URL) if err != nil { return nil, err } if data == nil || len(data) == 0 { return nil, fmt.Errorf("data empty in the encrypt") } request.Data = data } plainText := getBase64(request.Data) encryptedText, err := s.KmsService.Encrypt(ctx, request.Key, plainText) if err != nil { return nil, err } if encryptedText == "" { return nil, fmt.Errorf("encryptedText was empty") } if request.TargetURL != "" { err = upload(request.TargetURL, encryptedText) if err != nil { return nil, err } } encryptedData, err := base64.StdEncoding.DecodeString(encryptedText) if err != nil { return nil, err } return &kms.EncryptResponse{ EncryptedData: encryptedData, EncryptedText: encryptedText, }, nil } func (s *service) Decrypt(ctx context.Context, request *kms.DecryptRequest) (*kms.DecryptResponse, error) { if request.URL != "" { resource := url.NewResource(request.URL) base64Text, err := resource.DownloadBase64() if err != nil { return nil, err } request.Data = []byte(base64Text) } else if len(request.Data) > 0 { base64Text := getBase64(request.Data) request.Data = []byte(base64Text) } plainText := string(request.Data) text, err := s.KmsService.Decrypt(ctx, request.Key, plainText) if err != nil { return nil, err } if text == "" { return nil, fmt.Errorf("no text in the decrypt") } data, err := base64.StdEncoding.DecodeString(text) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("failed to base64 decode text %v", text)) } decryptResponse := &kms.DecryptResponse{ Data: data, Text: text, } return decryptResponse, nil } func getBase64(data []byte) string { plainText := string(data) isBase64 := false if _, err := base64.StdEncoding.DecodeString(string(data)); err == nil { isBase64 = true } if !isBase64 { plainText = base64.StdEncoding.EncodeToString(data) } return plainText } func upload(targetURL string, encryptedText string) error { storageService, err := storage.NewServiceForURL(targetURL, "") if err != nil { return errors.Wrap(err, fmt.Sprintf("failed to get storage for url %v", targetURL)) } return storageService.Upload(targetURL, bytes.NewReader([]byte(encryptedText))) } func getDataFromURL(URL string) ([]byte, error) { storageService, err := storage.NewServiceForURL(URL, "") if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("failed to create storage for url: %v", URL)) } reader, err := storageService.DownloadWithURL(URL) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("failed to download url: %v", URL)) } data, err := ioutil.ReadAll(reader) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("failed to read data from %v", URL)) } return data, nil } toolbox-0.33.2/kms/gcp/service_test.go000066400000000000000000000156131374110251100176570ustar00rootroot00000000000000package gcp import ( "encoding/base64" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/kms" "github.com/viant/toolbox/url" "golang.org/x/net/context" "strings" "testing" ) type MyTestConfig struct { Aaa string `json:",omitempty"` Bbb string `json:",omitempty"` Ccc string `json:",omitempty"` } func TestDecoder(t *testing.T) { // test decrypt read from non-base64 url { decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{} decryptRequest.Resource.URL = url.NewResource("test/data/config.txt", "").URL decoderFactory := toolbox.NewJSONDecoderFactory() mytestConfig := MyTestConfig{} service := service{KmsService: &testKmsService{}} service.Decode(context.Background(), &decryptRequest, decoderFactory, &mytestConfig) assert.Equal(t, mytestConfig.Aaa, "Test1") assert.Equal(t, mytestConfig.Bbb, "test2") assert.Equal(t, mytestConfig.Ccc, "test3") } // test decrypt read from base64 url { decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{} decryptRequest.Resource.URL = url.NewResource("test/data/config_base_64.txt", "").URL decoderFactory := toolbox.NewJSONDecoderFactory() mytestConfig := MyTestConfig{} service := service{KmsService: &testKmsService{}} service.Decode(context.Background(), &decryptRequest, decoderFactory, &mytestConfig) assert.Equal(t, mytestConfig.Aaa, "Test111") assert.Equal(t, mytestConfig.Bbb, "test222") assert.Equal(t, mytestConfig.Ccc, "test333") } } func TestEncrypt(t *testing.T) { //test base64 as input which from non-url { text := "path with?reserved+characters" request := kms.EncryptRequest{} encryptTextAfterBase64 := base64.StdEncoding.EncodeToString([]byte(text)) request.Resource = &kms.Resource{Data: []byte(encryptTextAfterBase64)} service := service{KmsService: &testKmsService{}} response, err := service.Encrypt(context.Background(), &request) assert.Nil(t, err) assert.Equal(t, response.EncryptedText, encryptTextAfterBase64) assert.Equal(t, string(response.EncryptedData), text) decryptRequest := kms.DecryptRequest{} decryptTextAfterBase64 := base64.StdEncoding.EncodeToString(response.EncryptedData) decryptRequest.Resource = &kms.Resource{Data: []byte(decryptTextAfterBase64)} decryptResponse, err := service.Decrypt(context.Background(), &decryptRequest) assert.Nil(t, err) assert.Equal(t, decryptResponse.Text, decryptTextAfterBase64) assert.Equal(t, string(decryptResponse.Data), text) } //test non-base64 as input which from non-url { text := "path with?reserved+characters2" request := kms.EncryptRequest{} request.Resource = &kms.Resource{Data: []byte(text)} service := service{KmsService: &testKmsService{}} response, err := service.Encrypt(context.Background(), &request) assert.Nil(t, err) assert.Equal(t, response.EncryptedText, base64.StdEncoding.EncodeToString([]byte(text))) assert.Equal(t, string(response.EncryptedData), text) decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{Data: response.EncryptedData} decryptResponse, err := service.Decrypt(context.Background(), &decryptRequest) assert.Nil(t, err) assert.Equal(t, decryptResponse.Text, base64.StdEncoding.EncodeToString([]byte(text))) assert.Equal(t, string(decryptResponse.Data), text) } //test base64 as input which from url { request := kms.EncryptRequest{} request.Resource = &kms.Resource{} request.Resource.URL = url.NewResource("test/data/test1.txt", "").URL request.TargetURL = url.NewResource("test/upload/upload1.txt").URL service := service{KmsService: &testKmsService{}} response, err := service.Encrypt(context.Background(), &request) assert.Nil(t, err) assert.Equal(t, response.EncryptedText, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test!!"))) assert.Equal(t, string(response.EncryptedData), "This is a encrypt/decrypt test!!") decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{} decryptRequest.Resource.URL = url.NewResource("test/data/test1.txt", "").URL decryptResponse, err := service.Decrypt(context.Background(), &decryptRequest) assert.Nil(t, err) assert.Equal(t, decryptResponse.Text, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test!!"))) assert.Equal(t, string(decryptResponse.Data), "This is a encrypt/decrypt test!!") } //test non-base64 as input which from url { request := kms.EncryptRequest{} request.Resource = &kms.Resource{} request.Resource.URL = url.NewResource("test/data/test2.txt", "").URL service := service{KmsService: &testKmsService{}} response, err := service.Encrypt(context.Background(), &request) assert.Nil(t, err) assert.Equal(t, response.EncryptedText, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test no2 !!!"))) assert.Equal(t, string(response.EncryptedData), "This is a encrypt/decrypt test no2 !!!") decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{} decryptRequest.Resource.URL = url.NewResource("test/data/test2.txt", "").URL decryptResponse, err := service.Decrypt(context.Background(), &decryptRequest) assert.Nil(t, err) assert.Equal(t, decryptResponse.Text, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test no2 !!!"))) assert.Equal(t, string(decryptResponse.Data), "This is a encrypt/decrypt test no2 !!!") } //test non-base64 as input for encrypt, based64 as input for decrypt { request := kms.EncryptRequest{} request.Resource = &kms.Resource{} request.Resource.URL = url.NewResource("test/data/test3.txt", "").URL service := service{KmsService: &testKmsService{}} response, err := service.Encrypt(context.Background(), &request) assert.Nil(t, err) assert.Equal(t, response.EncryptedText, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test no3 !!!@@@"))) assert.Equal(t, string(response.EncryptedData), "This is a encrypt/decrypt test no3 !!!@@@") decryptRequest := kms.DecryptRequest{} decryptRequest.Resource = &kms.Resource{} decryptRequest.Resource.URL = url.NewResource("test/data/test3_3.txt", "").URL decryptResponse, err := service.Decrypt(context.Background(), &decryptRequest) assert.Nil(t, err) assert.Equal(t, decryptResponse.Text, base64.StdEncoding.EncodeToString([]byte("This is a encrypt/decrypt test no3 !!!@@@"))) assert.Equal(t, string(decryptResponse.Data), "This is a encrypt/decrypt test no3 !!!@@@") } } type testKmsService struct { } func getPath(value string) string { processUrl := url.NewResource(value) processedPath := strings.Replace(processUrl.URL, "file:/", "", -1) return processedPath } func (testKmsService *testKmsService) Encrypt(ctx context.Context, key string, plainText string) (string, error) { return plainText, nil } func (testKmsService *testKmsService) Decrypt(ctx context.Context, key string, plainText string) (string, error) { return plainText, nil } toolbox-0.33.2/kms/gcp/test/000077500000000000000000000000001374110251100156025ustar00rootroot00000000000000toolbox-0.33.2/kms/gcp/test/data/000077500000000000000000000000001374110251100165135ustar00rootroot00000000000000toolbox-0.33.2/kms/gcp/test/data/config.txt000066400000000000000000000001071374110251100205170ustar00rootroot00000000000000{ "aaa":"Test1", "bbb":"test2", "ccc":"test3" }toolbox-0.33.2/kms/gcp/test/data/config_base_64.txt000066400000000000000000000001501374110251100220200ustar00rootroot00000000000000ewogICAgICAgICJhYWEiOiJUZXN0MTExIiwKICAgICAgICAiYmJiIjoidGVzdDIyMiIsCiAgICAgICAgImNjYyI6InRlc3QzMzMiCn0=toolbox-0.33.2/kms/gcp/test/data/test1.txt000066400000000000000000000000541374110251100203130ustar00rootroot00000000000000VGhpcyBpcyBhIGVuY3J5cHQvZGVjcnlwdCB0ZXN0ISE=toolbox-0.33.2/kms/gcp/test/data/test2.txt000066400000000000000000000000461374110251100203150ustar00rootroot00000000000000This is a encrypt/decrypt test no2 !!!toolbox-0.33.2/kms/gcp/test/data/test3.txt000066400000000000000000000000511374110251100203120ustar00rootroot00000000000000This is a encrypt/decrypt test no3 !!!@@@toolbox-0.33.2/kms/gcp/test/data/test3_3.txt000066400000000000000000000000701374110251100205350ustar00rootroot00000000000000VGhpcyBpcyBhIGVuY3J5cHQvZGVjcnlwdCB0ZXN0IG5vMyAhISFAQEA=toolbox-0.33.2/kms/gcp/test/upload/000077500000000000000000000000001374110251100170665ustar00rootroot00000000000000toolbox-0.33.2/kms/gcp/test/upload/upload1.txt000066400000000000000000000000541374110251100211730ustar00rootroot00000000000000VGhpcyBpcyBhIGVuY3J5cHQvZGVjcnlwdCB0ZXN0ISE=toolbox-0.33.2/kms/service.go000066400000000000000000000005361374110251100160450ustar00rootroot00000000000000package kms import ( "context" "github.com/viant/toolbox" ) type Service interface { Encrypt(context.Context, *EncryptRequest) (*EncryptResponse, error) Decrypt(context.Context, *DecryptRequest) (*DecryptResponse, error) Decode(ctx context.Context, decryptRequest *DecryptRequest, factory toolbox.DecoderFactory, target interface{}) error } toolbox-0.33.2/log_message.go000066400000000000000000000003261374110251100160750ustar00rootroot00000000000000package toolbox //LogMessage represent log message type LogMessage struct { MessageType string Message interface{} } //LogMessages represents log messages type LogMessages struct { Messages []LogMessage } toolbox-0.33.2/macro.go000066400000000000000000000122011374110251100147040ustar00rootroot00000000000000package toolbox import ( "encoding/json" "fmt" "io" "strings" ) //MacroEvaluator represents a macro expression evaluator, macros has the following format macro prefix [macro parameter] macro postfix type MacroEvaluator struct { Prefix, Postfix string ValueProviderRegistry ValueProviderRegistry } //HasMacro checks if candidate has a macro fragment func (e *MacroEvaluator) HasMacro(candidate string) bool { prefix, postfix := e.Prefix, e.Postfix prefixPosition := strings.Index(candidate, prefix) if prefixPosition == -1 { return false } postfixPosition := strings.Index(string(candidate[prefixPosition:]), postfix) return postfixPosition != -1 } func (e *MacroEvaluator) expandArguments(context Context, arguments *[]interface{}) error { //expanded macros within the macro for i, argument := range *arguments { if IsString(argument) { if argumentAsText, ok := argument.(string); ok { if e.HasMacro(argumentAsText) { expanded, err := e.Expand(context, argumentAsText) if err != nil { return fmt.Errorf("failed to expand argument: " + argumentAsText + " due to:\n\t" + err.Error()) } (*arguments)[i] = expanded } } } } return nil } func (e *MacroEvaluator) decodeArguments(context Context, decodedArguments string, macro string) ([]interface{}, error) { var arguments = make([]interface{}, 0) if len(decodedArguments) > 0 { decodedArguments = strings.Replace(decodedArguments, `\"`, `"`, len(decodedArguments)) decoder := json.NewDecoder(strings.NewReader(decodedArguments)) err := decoder.Decode(&arguments) if err != nil && err != io.EOF { return nil, fmt.Errorf("failed to process macro arguments: " + decodedArguments + " due to:\n\t" + err.Error()) } err = e.expandArguments(context, &arguments) if err != nil { return nil, err } } return arguments, nil } func (e *MacroEvaluator) extractMacro(input string) (success bool, macro, macroName, macroArguments string) { prefix, postfix := e.Prefix, e.Postfix var isInQuotes, argumentCount, previousChar, expectArguements, argumentStartPosition, argumentEndPosition = false, 0, "", false, 0, 0 prefixPosition := strings.Index(input, prefix) if prefixPosition == -1 { return false, "", "", "" } for i := prefixPosition + len(prefix); i < len(input); i++ { aChar := input[i : i+1] if i > 0 { previousChar = input[i-1 : i] } if strings.ContainsAny(aChar, " \b\n[") { expectArguements = true } if aChar == "\"" && previousChar != "\\" { isInQuotes = !isInQuotes } if !isInQuotes && aChar == "[" && previousChar != "\\" { if argumentCount == 0 { argumentStartPosition = i } argumentCount++ } if !isInQuotes && aChar == "]" && previousChar != "\\" { argumentEndPosition = i argumentCount-- } macro = macro + aChar if argumentCount == 0 { if aChar == postfix { break } if !expectArguements { macroName = macroName + aChar } } } if argumentStartPosition > 0 && argumentStartPosition < argumentEndPosition { macroArguments = input[argumentStartPosition : argumentEndPosition+1] } return true, prefix + macro, macroName, macroArguments } //Expand expands passed in input, it returns expanded value of any type or error func (e *MacroEvaluator) Expand(context Context, input string) (interface{}, error) { success, macro, macroName, macroArguments := e.extractMacro(input) if !success { return input, nil } valueProviderRegistry := e.ValueProviderRegistry if !valueProviderRegistry.Contains(macroName) { return nil, fmt.Errorf("failed to lookup macro: '%v' while processing: %v", macroName, input) } arguments, err := e.decodeArguments(context, macroArguments, macro) if err != nil { return nil, fmt.Errorf("failed expand macro: %v due to %v", macro, err.Error()) } valueProvider := valueProviderRegistry.Get(macroName) value, err := valueProvider.Get(context, arguments...) if err != nil { return nil, err } if len(macro) == len(input) { return value, nil } expandedMacro := fmt.Sprintf("%v", value) result := strings.Replace(input, macro, expandedMacro, 1) if e.HasMacro(result) { return e.Expand(context, result) } return result, nil } //NewMacroEvaluator returns a new macro evaluator func NewMacroEvaluator(prefix, postfix string, registry ValueProviderRegistry) *MacroEvaluator { return &MacroEvaluator{ Prefix: prefix, Postfix: postfix, ValueProviderRegistry: registry, } } //ExpandParameters expands passed in parameters as strings func ExpandParameters(macroEvaluator *MacroEvaluator, parameters map[string]string) error { for key := range parameters { value := parameters[key] if macroEvaluator.HasMacro(value) { textValue, err := macroEvaluator.Expand(nil, AsString(value)) if err != nil { return err } parameters[key] = AsString(textValue) } } return nil } //ExpandValue expands passed in value, it returns expanded string value or error func ExpandValue(macroEvaluator *MacroEvaluator, value string) (string, error) { if macroEvaluator.HasMacro(value) { expanded, err := macroEvaluator.Expand(nil, value) if err != nil { return "", err } return AsString(expanded), nil } return value, nil } toolbox-0.33.2/macro_test.go000066400000000000000000000103441374110251100157510ustar00rootroot00000000000000package toolbox_test import ( "fmt" "testing" "errors" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestMacroExpansion(t *testing.T) { valueRegistry := toolbox.NewValueProviderRegistry() valueRegistry.Register("abc", TestValueProvider{"Called with %v %v!", nil}) valueRegistry.Register("xyz", TestValueProvider{"XXXX", nil}) valueRegistry.Register("klm", TestValueProvider{"Called with %v %v!", errors.New("Test error")}) evaluator := toolbox.MacroEvaluator{ValueProviderRegistry: valueRegistry, Prefix: ""} { //simple macro test actual, err := evaluator.Expand(nil, "") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "Called with %v %v!", actual) } { //simple macro test actual, err := evaluator.Expand(nil, "< > ") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "< Called with %v %v!> XXXX", actual) } { //simple macro test actual, err := evaluator.Expand(nil, "") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "Called with %v %v!", actual) } { //simple macro with arguments actual, err := evaluator.Expand(nil, "") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "Called with 1 true!", actual) } { //simple macro with arguments actual, err := evaluator.Expand(nil, " ") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "Called with 1 true! Called with 2 false!", actual) } { //embeded macro with arguments actual, err := evaluator.Expand(nil, "\"]>") if err != nil { t.Errorf("failed expand macro %v", err.Error()) } assert.Equal(t, "Called with 1 Called with 10 11!!", actual) } { //value provider with error _, err := evaluator.Expand(nil, "\"]>") assert.NotNil(t, err, "macro argument value provider returns error") } { //value provider with error _, err := evaluator.Expand(nil, "") assert.NotNil(t, err, "value provider returns error") } { //simple macro with arguments _, err := evaluator.Expand(nil, "") assert.NotNil(t, err) } { //value provider with error _, err := evaluator.Expand(nil, "") assert.NotNil(t, err) } } type TestValueProvider struct { expandeWith string err error } func (t TestValueProvider) Init() error { return nil } func (t TestValueProvider) Get(context toolbox.Context, arguments ...interface{}) (interface{}, error) { if len(arguments) > 0 { return fmt.Sprintf(t.expandeWith, arguments...), t.err } return t.expandeWith, t.err } func (t TestValueProvider) Destroy() error { return nil } func TestExpandParameters(t *testing.T) { valueRegistry := toolbox.NewValueProviderRegistry() valueRegistry.Register("abc", TestValueProvider{"Called with %v %v!", nil}) valueRegistry.Register("klm", TestValueProvider{"Called with %v %v!", errors.New("Test error")}) evaluator := toolbox.MacroEvaluator{ValueProviderRegistry: valueRegistry, Prefix: ""} { aMap := map[string]string{ "k1": "!!", } err := toolbox.ExpandParameters(&evaluator, aMap) assert.NotNil(t, err) } { aMap := map[string]string{ "k1": "!!", } err := toolbox.ExpandParameters(&evaluator, aMap) assert.Nil(t, err) assert.Equal(t, "!Called with %v %v!!", aMap["k1"]) } } func TestExpandValue(t *testing.T) { valueRegistry := toolbox.NewValueProviderRegistry() valueRegistry.Register("abc", TestValueProvider{"Called with %v %v!", nil}) valueRegistry.Register("klm", TestValueProvider{"Called with %v %v!", errors.New("Test error")}) evaluator := toolbox.MacroEvaluator{ValueProviderRegistry: valueRegistry, Prefix: ""} { expanded, err := toolbox.ExpandValue(&evaluator, "!!") assert.Nil(t, err) assert.Equal(t, "!Called with %v %v!!", expanded) } { expanded, err := toolbox.ExpandValue(&evaluator, "!!") assert.Nil(t, err) assert.Equal(t, "!!", expanded) } { _, err := toolbox.ExpandValue(&evaluator, "") assert.NotNil(t, err) } } toolbox-0.33.2/mime_type.go000066400000000000000000000011141374110251100155740ustar00rootroot00000000000000package toolbox const ( //JSONMimeType JSON mime type constant JSONMimeType = "text/json" //CSVMimeType csv mime type constant CSVMimeType = "text/csv" //TSVMimeType tab separated mime type constant TSVMimeType = "text/tsv" //TextMimeType mime type constant TextMimeType = "text/sql" ) //FileExtensionMimeType json, csv, tsc, sql mime types. var FileExtensionMimeType = map[string]string{ "json": JSONMimeType, "csv": CSVMimeType, "tsv": TSVMimeType, "sql": TextMimeType, "html": "text/html", "js": "text/javascript", "jpg": "image/jpeg", "png": "image/png", } toolbox-0.33.2/os_helper.go000066400000000000000000000022241374110251100155670ustar00rootroot00000000000000package toolbox import ( "fmt" "os" "path" "strings" ) var dirMode os.FileMode = 0744 // RemoveFileIfExist remove file if exists func RemoveFileIfExist(filenames ...string) error { for _, filename := range filenames { if !FileExists(filename) { continue } err := os.Remove(filename) if err != nil { return err } } return nil } // FileExists checks if file exists func FileExists(filename string) bool { if _, err := os.Stat(filename); err != nil { return false } return true } // IsDirectory checks if file is directory func IsDirectory(location string) bool { if stat, _ := os.Stat(location); stat != nil { return stat.IsDir() } return false } // CreateDirIfNotExist creates directory if they do not exist func CreateDirIfNotExist(dirs ...string) error { for _, dir := range dirs { if len(dir) > 1 && strings.HasSuffix(dir, "/") { dir = dir[:len(dir)-1] } parent, _ := path.Split(dir) if parent != "/" && parent != dir { CreateDirIfNotExist(parent) } if !FileExists(dir) { err := os.Mkdir(dir, dirMode) if err != nil { return fmt.Errorf("failed to create dir %v %v", dir, err) } } } return nil } toolbox-0.33.2/os_helper_test.go000066400000000000000000000005461374110251100166330ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestCreateDirIfNotExist(t *testing.T) { dir := "/tmp/abc" toolbox.RemoveFileIfExist(dir) toolbox.CreateDirIfNotExist(dir) assert.True(t, toolbox.FileExists(dir)) toolbox.RemoveFileIfExist(dir) assert.False(t, toolbox.FileExists(dir)) } toolbox-0.33.2/predicate.go000066400000000000000000000002351374110251100155470ustar00rootroot00000000000000package toolbox //Predicate represents a generic predicate type Predicate interface { //Apply checks if predicate is true Apply(value interface{}) bool } toolbox-0.33.2/predicates.go000066400000000000000000000126731374110251100157430ustar00rootroot00000000000000package toolbox import ( "fmt" "reflect" "strings" "time" ) //TrueProvider represents a true provider var TrueProvider = func(input interface{}) bool { return true } type withinSecPredicate struct { baseTime time.Time deltaInSeconds int dateLayout string actual string elapsed time.Duration maxAllowedDelay time.Duration } func (p *withinSecPredicate) String() string { return fmt.Sprintf("(elapsed: %d, max allowed delay: %d)\n", int(p.elapsed), int(p.maxAllowedDelay)) } //Apply returns true if passed in time is within deltaInSeconds from baseTime func (p *withinSecPredicate) Apply(value interface{}) bool { timeValue, err := ToTime(value, p.dateLayout) if err != nil { return false } elapsed := timeValue.Sub(p.baseTime) if elapsed < 0 { elapsed *= -1 } var maxAllowedDelay = time.Duration(p.deltaInSeconds) * time.Second var passed = maxAllowedDelay >= elapsed if !passed { p.elapsed = elapsed p.maxAllowedDelay = maxAllowedDelay } return passed } func (p *withinSecPredicate) ToString() string { return fmt.Sprintf(" %v within %v s", p.baseTime, p.deltaInSeconds) } //NewWithinPredicate returns new NewWithinPredicate predicate, it takes base time, delta in second, and dateLayout func NewWithinPredicate(baseTime time.Time, deltaInSeconds int, dateLayout string) Predicate { return &withinSecPredicate{ baseTime: baseTime, deltaInSeconds: deltaInSeconds, dateLayout: dateLayout, } } type betweenPredicate struct { from float64 to float64 } func (p *betweenPredicate) Apply(value interface{}) bool { floatValue := AsFloat(value) return floatValue >= p.from && floatValue <= p.to } func (p *betweenPredicate) String() string { return fmt.Sprintf("x BETWEEN %v AND %v", p.from, p.to) } //NewBetweenPredicate creates a new BETWEEN predicate, it takes from, and to. func NewBetweenPredicate(from, to interface{}) Predicate { return &betweenPredicate{ from: AsFloat(from), to: AsFloat(to), } } type inPredicate struct { predicate Predicate } func (p *inPredicate) Apply(value interface{}) bool { return p.predicate.Apply(value) } //NewInPredicate creates a new IN predicate func NewInPredicate(values ...interface{}) Predicate { converted, kind := DiscoverCollectionValuesAndKind(values) switch kind { case reflect.Int: predicate := inIntPredicate{values: make(map[int]bool)} SliceToMap(converted, predicate.values, func(item interface{}) int { return AsInt(item) }, TrueProvider) return &predicate case reflect.Float64: predicate := inFloatPredicate{values: make(map[float64]bool)} SliceToMap(converted, predicate.values, func(item interface{}) float64 { return AsFloat(item) }, TrueProvider) return &predicate default: predicate := inStringPredicate{values: make(map[string]bool)} SliceToMap(converted, predicate.values, func(item interface{}) string { return AsString(item) }, TrueProvider) return &predicate } } type inFloatPredicate struct { values map[float64]bool } func (p *inFloatPredicate) Apply(value interface{}) bool { candidate := AsFloat(value) return p.values[candidate] } type inIntPredicate struct { values map[int]bool } func (p *inIntPredicate) Apply(value interface{}) bool { candidate := AsInt(value) return p.values[int(candidate)] } type inStringPredicate struct { values map[string]bool } func (p *inStringPredicate) Apply(value interface{}) bool { candidate := AsString(value) return p.values[candidate] } type numericComparablePredicate struct { rightOperand float64 operator string } func (p *numericComparablePredicate) Apply(value interface{}) bool { leftOperand := AsFloat(value) switch p.operator { case ">": return leftOperand > p.rightOperand case ">=": return leftOperand >= p.rightOperand case "<": return leftOperand < p.rightOperand case "<=": return leftOperand <= p.rightOperand case "=": return leftOperand == p.rightOperand case "!=": return leftOperand != p.rightOperand } return false } type stringComparablePredicate struct { rightOperand string operator string } func (p *stringComparablePredicate) Apply(value interface{}) bool { leftOperand := AsString(value) switch p.operator { case "=": return leftOperand == p.rightOperand case "!=": return leftOperand != p.rightOperand } return false } //NewComparablePredicate create a new comparable predicate for =, !=, >=, <= func NewComparablePredicate(operator string, leftOperand interface{}) Predicate { if CanConvertToFloat(leftOperand) { return &numericComparablePredicate{AsFloat(leftOperand), operator} } return &stringComparablePredicate{AsString(leftOperand), operator} } type nilPredicate struct{} func (p *nilPredicate) Apply(value interface{}) bool { return value == nil || reflect.ValueOf(value).IsNil() } //NewNilPredicate returns a new nil predicate func NewNilPredicate() Predicate { return &nilPredicate{} } type likePredicate struct { matchingFragments []string } func (p *likePredicate) Apply(value interface{}) bool { textValue := strings.ToLower(AsString(value)) for _, matchingFragment := range p.matchingFragments { matchingIndex := strings.Index(textValue, matchingFragment) if matchingIndex == -1 { return false } if matchingIndex < len(textValue) { textValue = textValue[matchingIndex:] } } return true } //NewLikePredicate create a new like predicate func NewLikePredicate(matching string) Predicate { return &likePredicate{matchingFragments: strings.Split(strings.ToLower(matching), "%")} } toolbox-0.33.2/predicates_test.go000066400000000000000000000047721374110251100170030ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" "time" ) func TestWithinPredicate(t *testing.T) { targetTime := time.Unix(1465009041, 0) predicate := toolbox.NewWithinPredicate(targetTime, 2, "") timeValue := time.Unix(1465009042, 0) assert.True(t, predicate.Apply(timeValue)) timeValue = time.Unix(1465009044, 0) assert.False(t, predicate.Apply(timeValue)) } func TestBetweenPredicate(t *testing.T) { predicate := toolbox.NewBetweenPredicate(10, 20) assert.False(t, predicate.Apply(9)) assert.True(t, predicate.Apply(10)) assert.True(t, predicate.Apply(11)) assert.False(t, predicate.Apply(21)) } func TestInPredicate(t *testing.T) { { predicate := toolbox.NewInPredicate("10", 20, "a") assert.False(t, predicate.Apply(9)) assert.True(t, predicate.Apply(10)) assert.False(t, predicate.Apply(15)) assert.True(t, predicate.Apply("a")) assert.True(t, predicate.Apply(20)) assert.False(t, predicate.Apply(21)) } } func TestComparablePredicate(t *testing.T) { { predicate := toolbox.NewComparablePredicate(">", "1") assert.True(t, predicate.Apply(3)) assert.False(t, predicate.Apply(1)) } { predicate := toolbox.NewComparablePredicate("<", "1") assert.True(t, predicate.Apply(0)) assert.False(t, predicate.Apply(3)) } { predicate := toolbox.NewComparablePredicate("!=", "1") assert.True(t, predicate.Apply(0)) assert.False(t, predicate.Apply(1)) } } func TestNewLikePredicate(t *testing.T) { { predicate := toolbox.NewLikePredicate("abc%efg") assert.False(t, predicate.Apply("abefg")) assert.True(t, predicate.Apply("abcefg")) } { predicate := toolbox.NewLikePredicate("abc%") assert.True(t, predicate.Apply("abcfg")) } } func TestNewComparablePredicate(t *testing.T) { { predicate := toolbox.NewComparablePredicate("=", "abc") assert.True(t, predicate.Apply("abc")) assert.False(t, predicate.Apply("abdc")) } { predicate := toolbox.NewComparablePredicate("!=", "abc") assert.True(t, predicate.Apply("abcc")) assert.False(t, predicate.Apply("abc")) } { predicate := toolbox.NewComparablePredicate(">=", 3) assert.True(t, predicate.Apply(10)) assert.False(t, predicate.Apply(1)) } { predicate := toolbox.NewComparablePredicate("<=", 3) assert.True(t, predicate.Apply(1)) assert.False(t, predicate.Apply(10)) } } func TestNewInPredicate(t *testing.T) { predicate := toolbox.NewInPredicate(1.2, 1.5) assert.True(t, predicate.Apply("1.2")) assert.False(t, predicate.Apply("1.1")) } toolbox-0.33.2/ranger.go000066400000000000000000000002551374110251100150670ustar00rootroot00000000000000package toolbox //Ranger represents an abstraction that has ability range over collection item type Ranger interface { Range(func(item interface{}) (bool, error)) error } toolbox-0.33.2/sampler/000077500000000000000000000000001374110251100147235ustar00rootroot00000000000000toolbox-0.33.2/sampler/service.go000066400000000000000000000015771374110251100167240ustar00rootroot00000000000000package sampler import ( "math/rand" "sync" "time" ) //Service represents a sampler service type Service struct { rand *rand.Rand acceptThreshold int32 mux sync.Mutex PCT float64 } //Accept accept sample meeting accept gaol PCT func (s *Service) Accept() bool { s.mux.Lock() defer s.mux.Unlock() n := s.rand.Int31n(100000) return n < s.acceptThreshold } //Accept accept with threshold with PCT being ignored (same as PCT=100) func (s *Service) AcceptWithThreshold(threshold float64) bool { s.mux.Lock() defer s.mux.Unlock() n := s.rand.Int31n(100000) return n < int32(threshold*1000.0) } //New creates a pct sampler func New(acceptPCT float64) *Service { source := rand.NewSource(time.Now().UnixNano()) return &Service{ PCT: acceptPCT, rand: rand.New(source), acceptThreshold: int32(acceptPCT * 1000.0), } } toolbox-0.33.2/sampler/service_test.go000066400000000000000000000026571374110251100177630ustar00rootroot00000000000000package sampler import ( "github.com/stretchr/testify/assert" "testing" ) func TestService_Accept(t *testing.T) { useCases := []struct { description string goalPCT float64 testCount int }{ { description: "50% goal", goalPCT: 50, testCount: 100000, }, { description: "95.1% goal", goalPCT: 95.1, testCount: 100000, }, } for _, useCase := range useCases { sampler := New(useCase.goalPCT) acceptCount := 0 for i := 0; i < useCase.testCount; i++ { if sampler.Accept() { acceptCount++ } } actualAcceptPCT := int(100.0 * (float64(acceptCount) / float64(useCase.testCount))) if actualAcceptPCT > int(useCase.goalPCT) { //allows -1 diff actualAcceptPCT-- } if actualAcceptPCT < int(useCase.goalPCT) { //allows +1 diff actualAcceptPCT++ } assert.Equal(t, int(useCase.goalPCT), actualAcceptPCT, useCase.description) } } func TestService_AcceptWithThreshold(t *testing.T) { sampler := New(100) // should not affect the result acceptCount := 0 testcount := 100000 for i := 0; i < testcount; i++ { if sampler.AcceptWithThreshold(50.0) { acceptCount++ } } actualAcceptPCT := int(100.0 * (float64(acceptCount) / float64(testcount))) if actualAcceptPCT > int(50.0) { //allows -1 diff actualAcceptPCT-- } if actualAcceptPCT < int(50.0) { //allows +1 diff actualAcceptPCT++ } assert.Equal(t, int(50), actualAcceptPCT, "accept with threshold off ") } toolbox-0.33.2/secret/000077500000000000000000000000001374110251100145455ustar00rootroot00000000000000toolbox-0.33.2/secret/README.md000066400000000000000000000070041374110251100160250ustar00rootroot00000000000000## Secret service ## Secret service provide convenient way of handling credentials. Service uses [credential config](./../cred/config.go) to store various provided credentials. ## Credentials retrieval Service supports the following form of **secret** to retrieve credentials: 1. URL i.e. mem://secret/localhost.json 2. Relative path i.e. localhost.json, in this case based directory will be used to lookup credential resource 3. Short name i.e. localhost, in this case based directory will be used to lookup credential resource and .json ext will be added. 4. Inline certificates or secrets that does not represent resource location are placed into cred.Config.Data **Base directory** can be file or URL, if empty '$HOME/.secret/' is used ```go service := New(baseDirectory, false) var secret = "localhost" service.GetCredentials(secret) ``` ## Credentials generation Service allows for interactive credential generation, in this scenario services asks a user for username and password for supplied secret. Optionally private key path can be supplied for pub key based auth. ```go privateKeyPath := ""//optional path secret := "localhost" service := New(baseDirectory, false) location, err := service.Create(secret, privateKeyPath) ``` The following example uses service in the interactive mode, which will try to lookup credential for supplied key, if failed it will ask a user for credential in terminal. ```go service := New(baseDirectory, true) config, err := service.GetOrCreate("xxxx") ``` ## Secret expansion Very common case for the application it to take encrypted credential to used wither username or password. For example while running terminal command we may need to provide super user password and sometimes other secret, in one command that we do not want to reveal to final user. Take the following code as example: ```go service := New(baseDirectory, true) secrets := NewSecrets() {//password expansion secrets["mysql"] = "~/.secret/mysql.json" input := "docker run --name db1 -e MYSQL_ROOT_PASSWORD=${mysql.password} -d mysql:tag" expaned, err := service.Expand(input, secrets) } {//username and password expansion secrets["pg"] = "~/.secret/pg.json" input := "docker run --name some-postgres -e POSTGRES_PASSWORD=${pg.password} -e POSTGRES_USER=${pg.username} -d postgres" expaned, err := service.Expand(input, secrets) } ``` Secrets represents credentials secret map defined as `type Secrets map[SecretKey]Secret` Here are some possible combination of secret map pairs. ```json { "git": "${env.HOME}/.secret/git.json", "github.com": "${env.HOME}/.secret/github.json", "github.private.com": "${env.HOME}/.secret/github-private.json", "**replace**": "${env.HOME}/.secret/git.json" } ``` The secret key can be static or dynamic. The first type in input/command is enclosed with either '*' or '#', the later is not. In the command corresponding dynamic key can be enclosed with the following 1) '${secretKey.password}' for password expansion i.e. command: **${git.password}** will expand to password from git secret key 2) '**' for password expansion i.e. command: `**git**`will expand to password from git secret key 3) '${secretKey.username}' for username expansion i.e. command: **${git.username}** will expand to username from git secret key 4) '##' for username expansion i.e. command: `##git##` will expand to username from git secret key toolbox-0.33.2/secret/helper.go000066400000000000000000000025031374110251100163530ustar00rootroot00000000000000package secret import ( "bufio" "errors" "fmt" "golang.org/x/crypto/ssh/terminal" "os" "strings" "syscall" "time" // "github.com/bgentry/speakeasy" ) //ReadingCredentialTimeout represents max time for providing CredentialsFromLocation var ReadingCredentialTimeout = time.Second * 45 var ReadUserAndPassword = func(timeout time.Duration) (user string, pass string, err error) { completed := make(chan bool) var reader = func() { defer func() { completed <- true }() var bytePassword, bytePassword2 []byte reader := bufio.NewReader(os.Stdin) fmt.Print("Enter Username: ") user, _ = reader.ReadString('\n') fmt.Print("Enter Password: ") bytePassword, err = terminal.ReadPassword(int(syscall.Stdin)) if err != nil { err = fmt.Errorf("failed to read password %v", err) return } fmt.Print("\nRetype Password: ") bytePassword2, err = terminal.ReadPassword(int(syscall.Stdin)) if err != nil { err = fmt.Errorf("failed to read password %v", err) return } password := string(bytePassword) if string(bytePassword2) != password { err = errors.New("password did not match") } } go reader() select { case <-completed: case <-time.After(timeout): err = fmt.Errorf("reading credential timeout") } user = strings.TrimSpace(user) pass = strings.TrimSpace(pass) return user, pass, err } toolbox-0.33.2/secret/helper_test.go000066400000000000000000000002031374110251100174050ustar00rootroot00000000000000package secret import ( "testing" "time" ) func TestSecretKey_Secret(t *testing.T) { ReadUserAndPassword(time.Second * 10) } toolbox-0.33.2/secret/secret.go000066400000000000000000000037151374110251100163670ustar00rootroot00000000000000package secret import ( "github.com/viant/toolbox/cred" "strings" ) //Secrets represents CredentialsFromLocation secret map type Secrets map[SecretKey]Secret //NewSecrets creates new secrets func NewSecrets(secrets map[string]string) Secrets { var result = make(map[SecretKey]Secret) if len(secrets) == 0 { return result } for k, v := range secrets { result[SecretKey(k)] = Secret(v) } return result } /** SecretKey represent secret key Take the following secrets as example:

	"secrets": {
		"git": "${env.HOME}/.secret/git.json",
		"github.com": "${env.HOME}/.secret/github.json",
		"github.private.com": "${env.HOME}/.secret/github-private.json",
		"**replace**": "${env.HOME}/.secret/git.json",
	}

The secret key can be static or dynamic. The first type is already enclosed with '*' or '#', the later is not. In the command corresponding dynamic key can be enclosed with the following '**' for password expansion i.e. command: **git** will expand to password from git secret key '##' for username expansion i.e. command: ##git## will expand to username from git secret key */ type SecretKey string //IsDynamic returns true if key is dynamic func (s SecretKey) IsDynamic() bool { return !(strings.HasPrefix(string(s), "*") || strings.HasPrefix(string(s), "#")) } //String returns secret key as string func (s SecretKey) String() string { return string(s) } //Get extracts username or password or JSON based on key type (# prefix for user, otherwise password or JSON) func (s SecretKey) Secret(cred *cred.Config) string { if strings.HasPrefix(s.String(), "#") || strings.HasSuffix(s.String(), ".username}") || strings.HasSuffix(s.String(), ".Username}") { return cred.Username } if cred.Password != "" { return cred.Password } return cred.Data } //Secret represents a secret type Secret string //IsLocation returns true if secret is a location func (s Secret) IsLocation() bool { return !strings.ContainsAny(string(s), "{}[]=+()@#^&*|") } toolbox-0.33.2/secret/secret_test.go000066400000000000000000000022431374110251100174210ustar00rootroot00000000000000package secret_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/secret" "testing" ) func TestSecret_IsLocation(t *testing.T) { { sec := secret.Secret("htttp://abc.com/secret.json") assert.True(t, sec.IsLocation()) } { sec := secret.Secret("{}") assert.False(t, sec.IsLocation()) } } func TestSecretKey_IsDynamic(t *testing.T) { { key := secret.SecretKey("**key1**") assert.False(t, key.IsDynamic()) } { key := secret.SecretKey("##key1##") assert.False(t, key.IsDynamic()) } { key := secret.SecretKey("key1") assert.True(t, key.IsDynamic()) } } func TestSecretKey_Secret(t *testing.T) { { //test user and password secret credConfig := &cred.Config{Username: "abc", Password: "pass"} { key := secret.SecretKey("**key1**") assert.EqualValues(t, "pass", key.Secret(credConfig)) } { key := secret.SecretKey("##key1##") assert.EqualValues(t, "abc", key.Secret(credConfig)) } } { //test json secret credConfig := &cred.Config{Username: "abc", Data: "{}"} { key := secret.SecretKey("**key1**") assert.EqualValues(t, "{}", key.Secret(credConfig)) } } } toolbox-0.33.2/secret/service.go000066400000000000000000000135641374110251100165450ustar00rootroot00000000000000package secret import ( "bytes" "errors" "fmt" "github.com/viant/toolbox" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/data" "github.com/viant/toolbox/storage" "github.com/viant/toolbox/url" "os" "path" "strings" "sync" ) //represents a secret service type Service struct { interactive bool baseDirectory string cache map[string]*cred.Config lock *sync.RWMutex } func (s *Service) CredentialsLocation(secret string) (string, error) { if secret == "" { return "", errors.New("secretLocation was empty") } if path.Ext(secret) == "" { secret += ".json" } if strings.HasPrefix(secret, "~") { secret = strings.Replace(secret, "~", os.Getenv("HOME"), 1) } currentDirectory, _ := os.Getwd() for _, candidate := range []string{secret, toolbox.URLPathJoin(currentDirectory, secret)} { if toolbox.FileExists(candidate) { return candidate, nil } } if strings.Contains(secret, ":/") { return secret, nil } return toolbox.URLPathJoin(s.baseDirectory, secret), nil } //Credentials returns credential config for supplied location. func (s *Service) CredentialsFromLocation(secret string) (*cred.Config, error) { secretLocation, err := s.CredentialsLocation(secret) if err != nil { return nil, err } s.lock.RLock() credConfig, has := s.cache[secretLocation] s.lock.RUnlock() if has { return credConfig, nil } resource := url.NewResource(secretLocation) configContent, err := resource.Download() if err != nil { return nil, fmt.Errorf("failed to open: '%v', due to: %v", secretLocation, err) } credConfig = &cred.Config{} if err = credConfig.LoadFromReader(bytes.NewReader(configContent), path.Ext(secretLocation)); err != nil { return nil, err } credConfig.Data = string(configContent) s.lock.Lock() defer s.lock.Unlock() s.cache[secretLocation] = credConfig return credConfig, nil } //GetOrCreate gets or creates credential func (s *Service) GetOrCreate(secret string) (*cred.Config, error) { if secret == "" { return nil, errors.New("secret was empty") } result, err := s.GetCredentials(secret) if err != nil && s.interactive && Secret(secret).IsLocation() { secretLocation, err := s.Create(secret, "") if err != nil { return nil, err } return s.GetCredentials(secretLocation) } return result, err } //Credentials returns credential config func (s *Service) GetCredentials(secret string) (*cred.Config, error) { if !Secret(secret).IsLocation() { var result = &cred.Config{Data: string(secret)} //try to load credential err := result.LoadFromReader(strings.NewReader(string(secret)), "") return result, err } return s.CredentialsFromLocation(secret) } func (s *Service) expandDynamicSecret(input string, key SecretKey, secret Secret) (string, error) { if !strings.Contains(input, key.String()) { return input, nil } credConfig, err := s.GetOrCreate(string(secret)) if err != nil { return "", err } createMap := data.NewMap() credInfo := map[string]interface{}{} _ = toolbox.DefaultConverter.AssignConverted(&credInfo, credInfo) credInfo["username"] = credConfig.Username credInfo["password"] = credConfig.Password createMap.Put(string(key), credInfo) passwordKey := fmt.Sprintf("**%v**", key) if count := strings.Count(input, passwordKey); count > 0 { secret := credConfig.Password if secret == "" { secret = credConfig.Data } input = strings.Replace(input, passwordKey, secret, count) } userKey := fmt.Sprintf("##%v##", key) if count := strings.Count(input, userKey); count > 0 { input = strings.Replace(input, userKey, credConfig.Username, count) } if index := strings.Index(input, "$"); index == -1 { return input, nil } return createMap.ExpandAsText(input), nil } func (s *Service) expandSecret(command string, key SecretKey, secret Secret) (string, error) { credConfig, err := s.GetOrCreate(string(secret)) if err != nil { return "", err } command = strings.Replace(command, key.String(), key.Secret(credConfig), 1) return command, nil } //Expand expands input credential keys with actual CredentialsFromLocation func (s *Service) Expand(input string, credentials map[SecretKey]Secret) (string, error) { if len(credentials) == 0 { return input, nil } var err error for k, v := range credentials { if strings.Contains(input, k.String()) { if k.IsDynamic() { input, err = s.expandDynamicSecret(input, k, v) } else { input, err = s.expandSecret(input, k, v) } if err != nil { return "", err } } } return input, nil } //Create creates a new credential config for supplied name func (s *Service) Create(name, privateKeyPath string) (string, error) { if strings.HasPrefix(privateKeyPath, "~") { privateKeyPath = strings.Replace(privateKeyPath, "~", os.Getenv("HOME"), 1) } username, password, err := ReadUserAndPassword(ReadingCredentialTimeout) if err != nil { return "", err } config := &cred.Config{ Username: username, Password: password, } if toolbox.FileExists(privateKeyPath) && !cred.IsKeyEncrypted(privateKeyPath) { config.PrivateKeyPath = privateKeyPath } var secretResource = url.NewResource(toolbox.URLPathJoin(s.baseDirectory, fmt.Sprintf("%v.json", name))) storageService, err := storage.NewServiceForURL(secretResource.URL, "") if err != nil { return "", err } buf := new(bytes.Buffer) err = config.Write(buf) if err != nil { return "", err } err = storageService.Upload(secretResource.URL, buf) return secretResource.URL, err } //NewSecretService creates a new secret service func New(baseDirectory string, interactive bool) *Service { if baseDirectory == "" { baseDirectory = path.Join(os.Getenv("HOME"), ".secret") } else if strings.HasPrefix(baseDirectory, "~") { baseDirectory = strings.Replace(baseDirectory, "~", path.Join(os.Getenv("HOME"), ".secret"), 1) } return &Service{ baseDirectory: baseDirectory, interactive: interactive, cache: make(map[string]*cred.Config), lock: &sync.RWMutex{}, } } toolbox-0.33.2/secret/service_test.go000066400000000000000000000121571374110251100176010ustar00rootroot00000000000000package secret import ( "bytes" "errors" "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/storage" "testing" "time" ) func setupData(baseDirectory string, data map[string]*cred.Config) error { storageService := storage.NewMemoryService() for k, v := range data { var buf = new(bytes.Buffer) if err := v.Write(buf); err != nil { return err } var URL = toolbox.URLPathJoin(baseDirectory, fmt.Sprintf("%v.json", k)) if err := storageService.Upload(URL, bytes.NewReader(buf.Bytes())); err != nil { return err } } return nil } func TestNew(t *testing.T) { var baseDirectory = "mem://secret" err := setupData(baseDirectory, map[string]*cred.Config{ "localhost": {Username: "user1", Password: "pass1"}, "10.3.3.12": {Username: "xxx", Password: "yyy"}, }) assert.Nil(t, err) service := New(baseDirectory, false) config, err := service.GetCredentials("localhost") if assert.Nil(t, err) { assert.EqualValues(t, "user1", config.Username) assert.EqualValues(t, "pass1", config.Password) } _, err = service.GetCredentials("nonexisting") assert.NotNil(t, err) } func TestService_Expand(t *testing.T) { var baseDirectory = "mem://secret" err := setupData(baseDirectory, map[string]*cred.Config{ "localhost": {Username: "user1", Password: "pass1"}, "10.3.3.12": {Username: "xxx", Data: `{"Key1":"abc"}`}, "github.com": {Username: "xxx", Password: "p"}, }) assert.Nil(t, err) service := New(baseDirectory, false) var original = ReadUserAndPassword defer func() { ReadUserAndPassword = original }() ReadUserAndPassword = func(timeout time.Duration) (string, string, error) { return "user1", "password2", nil } var useCases = []struct { Description string Interactive bool Matchable string Input string Credentials map[SecretKey]Secret Expect string HasError bool }{ { Description: "Password replacement with secret short name", Input: "**sudo**", Matchable: "", Credentials: map[SecretKey]Secret{ "**sudo**": "localhost", }, Expect: "pass1", }, { Description: "Password replacement with secret short name, explicit", Input: "${sudo.password}", Matchable: "", Credentials: map[SecretKey]Secret{ "sudo": "localhost", }, Expect: "pass1", }, { Description: "Username replacement with URL based secret", Input: "##sudo##", Matchable: "", Credentials: map[SecretKey]Secret{ "##sudo##": "mem://secret/localhost", }, Expect: "user1", }, { Description: "Username replacement with URL based secret and dynamic key", Input: "##sudo##", Matchable: "", Credentials: map[SecretKey]Secret{ "sudo": "mem://secret/localhost", }, Expect: "user1", }, { Description: "Username replacement with URL based secret and dynamic key - explicit", Input: "${sudo.username}", Matchable: "", Credentials: map[SecretKey]Secret{ "sudo": "mem://secret/localhost", }, Expect: "user1", }, { Description: "Non existing CredentialsFromLocation", Input: "**key**", Matchable: "", Credentials: map[SecretKey]Secret{ "key": "abc", }, HasError: true, }, { Description: "Interactive credential", Interactive: true, Input: "**key**", Matchable: "", Credentials: map[SecretKey]Secret{ "key": "test", }, Expect: "password2", }, } for _, useCase := range useCases { service.interactive = useCase.Interactive _, err := service.Expand(useCase.Input, useCase.Credentials) if useCase.HasError { assert.NotNil(t, err, useCase.Description) continue } if assert.Nil(t, err, useCase.Description) { } } } func TestService_Create(t *testing.T) { service := New("mem://secret", false) var original = ReadUserAndPassword defer func() { ReadUserAndPassword = original }() { //read user and password ReadUserAndPassword = func(timeout time.Duration) (string, string, error) { return "user1", "password2", nil } location, err := service.Create("test", "") assert.Nil(t, err) assert.EqualValues(t, "mem://secret/test.json", location) service := New("", false) config, err := service.GetCredentials(location) if assert.Nil(t, err) { assert.EqualValues(t, "user1", config.Username) assert.EqualValues(t, "password2", config.Password) } } { //Test interactive service.interactive = true ReadUserAndPassword = func(timeout time.Duration) (string, string, error) { return "user2", "password3", nil } config, err := service.GetOrCreate("xxxx") assert.Nil(t, err) if assert.Nil(t, err) { assert.EqualValues(t, "user2", config.Username) assert.EqualValues(t, "password3", config.Password) } } { //read user and password with error ReadUserAndPassword = func(timeout time.Duration) (string, string, error) { return "", "", errors.New("test") } _, err := service.Create("test", "") assert.NotNil(t, err) } } func TestSecret_IsLocation(t *testing.T) { secret := Secret("mem://github.com/viant/endly/workflow/docker/build/secret/build.json") assert.True(t, secret.IsLocation()) } toolbox-0.33.2/service_router.go000066400000000000000000000377071374110251100166650ustar00rootroot00000000000000package toolbox import ( "bytes" "errors" "fmt" "io/ioutil" "net" "net/http" "reflect" "strings" "time" ) const ( jsonContentType = "application/json" yamlContentTypeSuffix = "/yaml" textPlainContentType = "text/plain" contentTypeHeader = "Content-Type" ) const ( //MethodGet HTTP GET meothd MethodGet = "GET" MethodHead = "HEAD" MethodPost = "POST" MethodPut = "PUT" MethodPatch = "PATCH" // RFC 5789 MethodDelete = "DELETE" MethodOptions = "OPTIONS" MethodTrace = "TRACE" ) var httpMethods = map[string]bool{ MethodDelete: true, MethodGet: true, MethodPatch: true, MethodPost: true, MethodPut: true, MethodHead: true, MethodTrace: true, MethodOptions: true, } //HandlerInvoker method is responsible of passing required parameters to router handler. type HandlerInvoker func(serviceRouting *ServiceRouting, request *http.Request, response http.ResponseWriter, parameters map[string]interface{}) error //DefaultEncoderFactory - NewJSONEncoderFactory var DefaultEncoderFactory = NewJSONEncoderFactory() //DefaultDecoderFactory - NewJSONDecoderFactory var DefaultDecoderFactory = NewJSONDecoderFactory() //YamlDefaultEncoderFactory - NewYamlEncoderFactory var YamlDefaultEncoderFactory = NewYamlEncoderFactory() //YamlDefaultDecoderFactory - NewYamlDecoderFactory var YamlDefaultDecoderFactory = NewFlexYamlDecoderFactory() //ServiceRouting represents a simple web services routing rule, which is matched with http request type ServiceRouting struct { URI string //matching uri Handler interface{} //has to be func HTTPMethod string Parameters []string ContentTypeEncoders map[string]EncoderFactory //content type encoder factory ContentTypeDecoders map[string]DecoderFactory //content type decoder factory HandlerInvoker HandlerInvoker //optional function that will be used instead of reflection to invoke a handler. } func (sr ServiceRouting) getDecoderFactory(contentType string) DecoderFactory { if sr.ContentTypeDecoders != nil { if factory, found := sr.ContentTypeDecoders[contentType]; found { return factory } } if strings.HasSuffix(contentType, yamlContentTypeSuffix) { return YamlDefaultDecoderFactory } return DefaultDecoderFactory } func (sr ServiceRouting) getEncoderFactory(contentType string) EncoderFactory { if sr.ContentTypeDecoders != nil { if factory, found := sr.ContentTypeEncoders[contentType]; found { return factory } } if strings.HasSuffix(contentType, yamlContentTypeSuffix) { return YamlDefaultEncoderFactory } return DefaultEncoderFactory } func (sr ServiceRouting) extractParameterFromBody(parameterName string, targetType reflect.Type, request *http.Request) (interface{}, error) { targetValuePointer := reflect.New(targetType) contentType := getContentTypeOrJSONContentType(request.Header.Get(contentTypeHeader)) decoderFactory := sr.getDecoderFactory(contentType) body, err := ioutil.ReadAll(request.Body) if err != nil { return nil, err } decoder := decoderFactory.Create(bytes.NewReader(body)) if !strings.Contains(parameterName, ":") { err := decoder.Decode(targetValuePointer.Interface()) if err != nil { return nil, fmt.Errorf("unable to extract %T due to: %v, body: !%s!", targetValuePointer.Interface(), err, body) } } else { var valueMap = make(map[string]interface{}) pair := strings.SplitN(parameterName, ":", 2) valueMap[pair[1]] = targetValuePointer.Interface() err := decoder.Decode(&valueMap) if err != nil { return nil, fmt.Errorf("unable to extract %T due to %v", targetValuePointer.Interface(), err) } } return targetValuePointer.Interface(), nil } func (sr ServiceRouting) extractParameters(request *http.Request, response http.ResponseWriter) (map[string]interface{}, error) { var result = make(map[string]interface{}) _ = request.ParseForm() functionSignature := GetFuncSignature(sr.Handler) uriParameters, _ := ExtractURIParameters(sr.URI, request.RequestURI) for _, name := range sr.Parameters { value, found := uriParameters[name] if found { if strings.Contains(value, ",") { result[name] = strings.Split(value, ",") } else { result[name] = value } continue } value = request.Form.Get(name) if len(value) > 0 { result[name] = value } else { continue } } if HasSliceAnyElements(sr.Parameters, "@httpRequest") { result["@httpRequest"] = request } if HasSliceAnyElements(sr.Parameters, "@httpResponseWriter") { result["@httpResponseWriter"] = response } if request.ContentLength > 0 { for i, parameter := range sr.Parameters { if _, found := result[parameter]; !found { value, err := sr.extractParameterFromBody(parameter, functionSignature[i], request) if err != nil { return nil, fmt.Errorf("failed to extract parameters for %v %v due to %v", sr.HTTPMethod, sr.URI, err) } result[parameter] = value break } } } return result, nil } //ServiceRouter represents routing rule type ServiceRouter struct { serviceRouting []*ServiceRouting } func (r *ServiceRouter) match(request *http.Request) []*ServiceRouting { var result = make([]*ServiceRouting, 0) for _, candidate := range r.serviceRouting { if candidate.HTTPMethod == request.Method { _, matched := ExtractURIParameters(candidate.URI, request.RequestURI) if matched { result = append(result, candidate) } } } return result } func getContentTypeOrJSONContentType(contentType string) string { if strings.Contains(contentType, textPlainContentType) || strings.Contains(contentType, jsonContentType) || contentType == "" { return jsonContentType } return contentType } //Route matches service routing by http method , and number of parameters, then it call routing method, and sent back its response. func (r *ServiceRouter) Route(response http.ResponseWriter, request *http.Request) error { candidates := r.match(request) if len(candidates) == 0 { var uriTemplates = make([]string, 0) for _, routing := range r.serviceRouting { uriTemplates = append(uriTemplates, routing.URI) } return fmt.Errorf("failed to route request - unable to match %v with one of %v", request.RequestURI, strings.Join(uriTemplates, ",")) } var finalError error for _, serviceRouting := range candidates { parameterValues, err := serviceRouting.extractParameters(request, response) if err != nil { finalError = fmt.Errorf("unable to extract parameters due to %v", err) continue } if serviceRouting.HandlerInvoker != nil { err := serviceRouting.HandlerInvoker(serviceRouting, request, response, parameterValues) if err != nil { finalError = fmt.Errorf("unable to extract parameters due to %v", err) } continue } functionParameters, err := BuildFunctionParameters(serviceRouting.Handler, serviceRouting.Parameters, parameterValues) if err != nil { finalError = fmt.Errorf("unable to build function parameters %T due to %v", serviceRouting.Handler, err) continue } result := CallFunction(serviceRouting.Handler, functionParameters...) if len(result) > 0 { err = WriteServiceRoutingResponse(response, request, serviceRouting, result[0]) if err != nil { return fmt.Errorf("failed to write response response %v, due to %v", result[0], err) } return nil } response.Header().Set(contentTypeHeader, textPlainContentType) } if finalError != nil { return fmt.Errorf("failed to route request - %v", finalError) } return nil } //WriteServiceRoutingResponse writes service router response func WriteServiceRoutingResponse(response http.ResponseWriter, request *http.Request, serviceRouting *ServiceRouting, result interface{}) error { if result == nil { result = struct{}{} } statusCodeAccessor, ok := result.(StatucCodeAccessor) if ok { statusCode := statusCodeAccessor.GetStatusCode() if statusCode > 0 && statusCode != http.StatusOK { response.WriteHeader(statusCode) return nil } } contentTypeAccessor, ok := result.(ContentTypeAccessor) var responseContentType string if ok { responseContentType = contentTypeAccessor.GetContentType() } if responseContentType == "" { requestContentType := request.Header.Get(contentTypeHeader) responseContentType = getContentTypeOrJSONContentType(requestContentType) } encoderFactory := serviceRouting.getEncoderFactory(responseContentType) encoder := encoderFactory.Create(response) response.Header().Set(contentTypeHeader, responseContentType) err := encoder.Encode(result) if err != nil { return fmt.Errorf("failed to encode response %v, due to %v", response, err) } return nil if err != nil { return fmt.Errorf("failed to write response response %v, due to %v", result, err) } return nil } //WriteResponse writes response to response writer, it used encoder factory to encode passed in response to the writer, it sets back request contenttype to response. func (r *ServiceRouter) WriteResponse(encoderFactory EncoderFactory, response interface{}, request *http.Request, responseWriter http.ResponseWriter) error { requestContentType := request.Header.Get(contentTypeHeader) responseContentType := getContentTypeOrJSONContentType(requestContentType) encoder := encoderFactory.Create(responseWriter) responseWriter.Header().Set(contentTypeHeader, responseContentType) err := encoder.Encode(response) if err != nil { return fmt.Errorf("failed to encode response %v, due to %v", response, err) } return nil } //NewServiceRouter creates a new service router, is takes list of service routing as arguments func NewServiceRouter(serviceRouting ...ServiceRouting) *ServiceRouter { var routings = make([]*ServiceRouting, 0) for i := range serviceRouting { routings = append(routings, &serviceRouting[i]) } return &ServiceRouter{routings} } //RouteToService calls web service url, with passed in json request, and encodes http json response into passed response func RouteToService(method, url string, request, response interface{}, options ...*HttpOptions) (err error) { client, err := NewToolboxHTTPClient(options...) if err != nil { return err } return client.Request(method, url, request, response, NewJSONEncoderFactory(), NewJSONDecoderFactory()) } type HttpOptions struct { Key string Value interface{} } func NewHttpClient(options ...*HttpOptions) (*http.Client, error) { if len(options) == 0 { return http.DefaultClient, nil } var ( // Default values matching DefaultHttpClient RequestTimeoutMs = 30 * time.Second KeepAliveTimeMs = 30 * time.Second TLSHandshakeTimeoutMs = 10 * time.Second ExpectContinueTimeout = 1 * time.Second IdleConnTimeout = 90 * time.Second DualStack = true MaxIdleConnsPerHost = http.DefaultMaxIdleConnsPerHost MaxIdleConns = 100 FollowRedirects = true ResponseHeaderTimeoutMs time.Duration TimeoutMs time.Duration ) for _, option := range options { switch option.Key { case "RequestTimeoutMs": RequestTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond case "TimeoutMs": TimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond case "KeepAliveTimeMs": KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond case "TLSHandshakeTimeoutMs": KeepAliveTimeMs = time.Duration(AsInt(option.Value)) * time.Millisecond case "ResponseHeaderTimeoutMs": ResponseHeaderTimeoutMs = time.Duration(AsInt(option.Value)) * time.Millisecond case "MaxIdleConns": MaxIdleConns = AsInt(option.Value) case "MaxIdleConnsPerHost": MaxIdleConnsPerHost = AsInt(option.Value) case "DualStack": DualStack = AsBoolean(option.Value) case "FollowRedirects": FollowRedirects = AsBoolean(option.Value) default: return nil, fmt.Errorf("Invalid option: %v", option.Key) } } roundTripper := http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: RequestTimeoutMs, KeepAlive: KeepAliveTimeMs, DualStack: DualStack, }).DialContext, MaxIdleConns: MaxIdleConns, ExpectContinueTimeout: ExpectContinueTimeout, IdleConnTimeout: IdleConnTimeout, TLSHandshakeTimeout: TLSHandshakeTimeoutMs, MaxIdleConnsPerHost: MaxIdleConnsPerHost, ResponseHeaderTimeout: ResponseHeaderTimeoutMs, } client := &http.Client{ Transport: &roundTripper, Timeout: TimeoutMs, } if !FollowRedirects { client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } } return client, nil } // ToolboxHTTPClient contains preconfigured http client type ToolboxHTTPClient struct { httpClient *http.Client } // NewToolboxHTTPClient instantiate new client with provided options func NewToolboxHTTPClient(options ...*HttpOptions) (*ToolboxHTTPClient, error) { client, err := NewHttpClient(options...) if err != nil { return nil, err } return &ToolboxHTTPClient{client}, nil } // Request sends http request using the existing client func (c *ToolboxHTTPClient) Request(method, url string, request, response interface{}, encoderFactory EncoderFactory, decoderFactory DecoderFactory) (err error) { if _, found := httpMethods[strings.ToUpper(method)]; !found { return errors.New("unsupported method:" + method) } var buffer *bytes.Buffer if request != nil { buffer = new(bytes.Buffer) if IsString(request) { buffer.Write([]byte(AsString(request))) } else { err := encoderFactory.Create(buffer).Encode(&request) if err != nil { return fmt.Errorf("failed to encode request: %v due to ", err) } } } var serverResponse *http.Response var httpRequest *http.Request httpMethod := strings.ToUpper(method) if request != nil { httpRequest, err = http.NewRequest(httpMethod, url, buffer) if err != nil { return err } httpRequest.Header.Set(contentTypeHeader, jsonContentType) } else { httpRequest, err = http.NewRequest(httpMethod, url, nil) if err != nil { return err } } serverResponse, err = c.httpClient.Do(httpRequest) if serverResponse != nil { // must close we have serverResponse to avoid fd leak defer serverResponse.Body.Close() } if err != nil && serverResponse != nil { return fmt.Errorf("failed to get response %v %v", err, serverResponse.Header.Get("error")) } if response != nil { updateResponse(serverResponse, response) if serverResponse == nil { return fmt.Errorf("failed to receive response %v", err) } var errorPrefix = fmt.Sprintf("failed to process response: %v, ", serverResponse.StatusCode) body, err := ioutil.ReadAll(serverResponse.Body) if err != nil { return fmt.Errorf("%v unable read body %v", errorPrefix, err) } if len(body) == 0 { return fmt.Errorf("%v response body was empty", errorPrefix) } if serverResponse.StatusCode == http.StatusNotFound { updateResponse(serverResponse, response) return nil } if int(serverResponse.StatusCode/100)*100 == http.StatusInternalServerError { return errors.New(string(body)) } err = decoderFactory.Create(strings.NewReader(string(body))).Decode(response) if err != nil { return fmt.Errorf("%v. unable decode response as %T: body: %v: %v", errorPrefix, response, string(body), err) } updateResponse(serverResponse, response) } return nil } //StatucCodeMutator client side reponse optional interface type StatucCodeMutator interface { SetStatusCode(code int) } //StatucCodeAccessor server side response accessor type StatucCodeAccessor interface { GetStatusCode() int } //ContentTypeMutator client side reponse optional interface type ContentTypeMutator interface { SetContentType(contentType string) } //ContentTypeAccessor server side response accessor type ContentTypeAccessor interface { GetContentType() string } //updateResponse update response with content type and status code if applicable func updateResponse(httpResponse *http.Response, response interface{}) { if response == nil { return } statusCodeMutator, ok := response.(StatucCodeMutator) if ok { statusCodeMutator.SetStatusCode(httpResponse.StatusCode) } contentTypeMutator, ok := response.(ContentTypeMutator) if ok { contentTypeMutator.SetContentType(httpResponse.Header.Get(contentTypeHeader)) } } toolbox-0.33.2/service_router_test.go000066400000000000000000000110071374110251100177050ustar00rootroot00000000000000package toolbox_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "log" "net/http" "testing" "time" ) type ReverseService struct{} func (this ReverseService) Reverse(values []int) []int { var result = make([]int, 0) for i := len(values) - 1; i >= 0; i-- { result = append(result, values[i]) } return result } func (this ReverseService) Reverse2(values []int) []int { var result = make([]int, 0) for i := len(values) - 1; i >= 0; i-- { result = append(result, values[i]) } return result } var ReverseInvoker = func(serviceRouting *toolbox.ServiceRouting, request *http.Request, response http.ResponseWriter, uriParameters map[string]interface{}) error { var function = serviceRouting.Handler.(func(values []int) []int) idsParam := uriParameters["ids"] ids := idsParam.([]string) values := make([]int, 0) for _, item := range ids { values = append(values, toolbox.AsInt(item)) } var result = function(values) err := toolbox.WriteServiceRoutingResponse(response, request, serviceRouting, result) if err != nil { return err } return nil } func StartServer(port string, t *testing.T) { service := ReverseService{} router := toolbox.NewServiceRouter( toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/reverse/{ids}", Handler: service.Reverse, Parameters: []string{"ids"}, }, toolbox.ServiceRouting{ HTTPMethod: "POST", URI: "/v1/reverse/", Handler: service.Reverse, Parameters: []string{"ids"}, }, toolbox.ServiceRouting{ HTTPMethod: "DELETE", URI: "/v1/delete/{ids}", Handler: service.Reverse, Parameters: []string{"ids"}, }, toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/reverse2/{ids}", Handler: service.Reverse, Parameters: []string{"ids"}, HandlerInvoker: ReverseInvoker, }, toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/tasks", Parameters: []string{"status"}, Handler: func(status string) map[string]interface{} { var result = map[string]interface{}{ "STATUS": status, "ABc": 101, } return result }, }, toolbox.ServiceRouting{ HTTPMethod: "GET", URI: "/v1/tasks/{ids}", Parameters: []string{"ids"}, Handler: func(ids ...string) map[string]interface{} { var result = map[string]interface{}{ "STATUS": ids, "ABc": 102, } return result }, }, ) http.HandleFunc("/v1/", func(writer http.ResponseWriter, reader *http.Request) { err := router.Route(writer, reader) assert.Nil(t, err) }) fmt.Printf("Started test server on port %v\n", port) log.Fatal(http.ListenAndServe(":"+port, nil)) } func TestServiceRouter(t *testing.T) { go func() { StartServer("8082", t) }() time.Sleep(2 * time.Second) { var result = map[string]interface{}{} err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/tasks/1,2,3", nil, &result) if err != nil { t.Errorf("failed to send get request %v", err) } assert.EqualValues(t, []interface{}{"1", "2", "3"}, result["STATUS"]) } // { var result = map[string]interface{}{} err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/tasks?status=OK", nil, &result) if err != nil { t.Errorf("failed to send get request %v", err) } assert.EqualValues(t, "OK", result["STATUS"]) } var result = make([]int, 0) { err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/reverse/1,7,3", nil, &result) if err != nil { t.Errorf("failed to send get request %v", err) } assert.EqualValues(t, []int{3, 7, 1}, result) } { err := toolbox.RouteToService("post", "http://127.0.0.1:8082/v1/reverse/", []int{1, 7, 3}, &result) if err != nil { t.Errorf("failed to send get request %v", err) } assert.EqualValues(t, []int{3, 7, 1}, result) } { err := toolbox.RouteToService("delete", "http://127.0.0.1:8082/v1/delete/", []int{1, 7, 3}, &result) if err != nil { t.Errorf("failed to send delete request %v", err) } assert.EqualValues(t, []int{3, 7, 1}, result) } { err := toolbox.RouteToService("delete", "http://127.0.0.1:8082/v1/delete/1,7,3", nil, &result) if err != nil { t.Errorf("failed to send delete request %v", err) } assert.EqualValues(t, []int{3, 7, 1}, result) } { //Test custom handler invocation without reflection err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/reverse2/1,7,3", nil, &result) if err != nil { t.Errorf("failed to send delete request %v", err) } assert.EqualValues(t, []int{3, 7, 1}, result) } } toolbox-0.33.2/ssh/000077500000000000000000000000001374110251100140555ustar00rootroot00000000000000toolbox-0.33.2/ssh/replay_command.go000066400000000000000000000105561374110251100174050ustar00rootroot00000000000000package ssh import ( "fmt" "github.com/viant/toolbox" "io/ioutil" "os" "path" "sort" "strings" ) //ReplayCommand represent a replay command type ReplayCommand struct { Stdin string Index int Stdout []string Error string } //replayCommands represnets command grouped by stdin type ReplayCommands struct { Commands map[string]*ReplayCommand Keys []string BaseDir string } //Register register stdin and corresponding stdout conversation func (c *ReplayCommands) Register(stdin, stdout string) { if _, has := c.Commands[stdin]; !has { c.Commands[stdin] = &ReplayCommand{ Stdin: stdin, Stdout: make([]string, 0), } c.Keys = append(c.Keys, stdin) } c.Commands[stdin].Stdout = append(c.Commands[stdin].Stdout, stdout) } //return stdout pointed by index and increases index or empty string if exhausted func (c *ReplayCommands) Next(stdin string) string { var stdout = c.Commands[stdin].Stdout var index = c.Commands[stdin].Index if index < len(stdout) { c.Commands[stdin].Index++ return stdout[index] } return "" } func (c *ReplayCommands) Enable(source interface{}) (err error) { switch value := source.(type) { case *service: value.replayCommands = c value.recordSession = true case *multiCommandSession: value.replayCommands = c value.recordSession = true default: err = fmt.Errorf("unsupported type: %T", source) } return err } func (c *ReplayCommands) Disable(source interface{}) (err error) { switch value := source.(type) { case *service: value.replayCommands = nil value.recordSession = false case *multiCommandSession: value.replayCommands = nil value.recordSession = false default: err = fmt.Errorf("unsupported type: %T", source) } return err } //Store stores replay command in the base directory func (c *ReplayCommands) Store() error { err := toolbox.CreateDirIfNotExist(c.BaseDir) if err != nil { return err } for i, key := range c.Keys { var command = c.Commands[key] var filenamePrefix = path.Join(c.BaseDir, fmt.Sprintf("%03d", i+1)) var stdinFilename = filenamePrefix + "_000.stdin" err := ioutil.WriteFile(stdinFilename, []byte(command.Stdin), 0644) if err != nil { return err } for j, stdout := range command.Stdout { var stdoutFilename = fmt.Sprintf("%v_%03d.stdout", filenamePrefix, j+1) err := ioutil.WriteFile(stdoutFilename, []byte(stdout), 0644) if err != nil { return err } } } return nil } //Load loads replay command from base directory func (c *ReplayCommands) Load() error { parent, err := os.Open(c.BaseDir) if err != nil { return err } files, err := parent.Readdir(1000) if err != nil { return err } var stdinMap = make(map[string]string) var stdoutMap = make(map[string]string) for _, candidate := range files { ext := path.Ext(candidate.Name()) var contentMap map[string]string if ext == ".stdin" { contentMap = stdinMap } else if ext == ".stdout" { contentMap = stdoutMap } else { continue } var filename = path.Join(c.BaseDir, candidate.Name()) content, err := ioutil.ReadFile(filename) if err != nil { return nil } contentMap[candidate.Name()] = string(content) } for key, stdin := range stdinMap { var prefix = key[:len(key)-10] var candidateKeys = toolbox.MapKeysToStringSlice(stdoutMap) sort.Strings(candidateKeys) for _, candidateKey := range candidateKeys { if strings.HasPrefix(candidateKey, prefix) { stdout := stdoutMap[candidateKey] c.Register(stdin, stdout) } } } return nil } //Shell returns command shell func (c *ReplayCommands) Shell() string { for _, candidate := range c.Commands { if strings.HasPrefix(candidate.Stdin, "PS1=") && len(candidate.Stdout) > 0 { return candidate.Stdout[0] } } return "" } //System returns system name func (c *ReplayCommands) System() string { for _, candidate := range c.Commands { if strings.HasPrefix(candidate.Stdin, "uname -s") && len(candidate.Stdout) > 0 { return strings.ToLower(candidate.Stdout[0]) } } return "" } //NewReplayCommands create a new replay commands or error if provided basedir does not exists and can not be created func NewReplayCommands(basedir string) (*ReplayCommands, error) { if !toolbox.FileExists(basedir) { err := os.MkdirAll(basedir, 0744) if err != nil { return nil, err } } return &ReplayCommands{ Commands: make(map[string]*ReplayCommand), Keys: make([]string, 0), BaseDir: basedir, }, nil } toolbox-0.33.2/ssh/service.go000066400000000000000000000155441374110251100160550ustar00rootroot00000000000000package ssh import ( "bytes" "fmt" "github.com/pkg/errors" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/storage" "golang.org/x/crypto/ssh" "io" "net" "os" "path" "strings" "sync" "time" ) type ( //Service represents ssh service Service interface { //Service returns a service wrapper Client() *ssh.Client //OpenMultiCommandSession opens multi command session OpenMultiCommandSession(config *SessionConfig) (MultiCommandSession, error) //Run runs supplied command Run(command string) error //Upload uploads provided content to specified destination //Deprecated: please consider using https://github.com/viant/afs/tree/master/scp Upload(destination string, mode os.FileMode, content []byte) error //Download downloads content from specified source. //Deprecated: please consider using https://github.com/viant/afs/tree/master/scp Download(source string) ([]byte, error) //OpenTunnel opens a tunnel between local to remote for network traffic. OpenTunnel(localAddress, remoteAddress string) error NewSession() (*ssh.Session, error) Close() error } ) //service represnt SSH service type service struct { host string client *ssh.Client forwarding []*Tunnel replayCommands *ReplayCommands recordSession bool config *ssh.ClientConfig } //Service returns undelying ssh Service func (c *service) Client() *ssh.Client { return c.client } //Service returns undelying ssh Service func (c *service) NewSession() (*ssh.Session, error) { return c.client.NewSession() } //MultiCommandSession create a new MultiCommandSession func (c *service) OpenMultiCommandSession(config *SessionConfig) (MultiCommandSession, error) { return newMultiCommandSession(c, config, c.replayCommands, c.recordSession) } func (c *service) Run(command string) error { session, err := c.client.NewSession() if err != nil { panic("failed to create session: " + err.Error()) } defer session.Close() return session.Run(command) } func (c *service) transferData(payload []byte, createFileCmd string, writer io.Writer, errors chan error, waitGroup *sync.WaitGroup) { const endSequence = "\x00" defer waitGroup.Done() _, err := fmt.Fprint(writer, createFileCmd) if err != nil { errors <- err return } _, err = io.Copy(writer, bytes.NewReader(payload)) if err != nil { errors <- err return } if _, err = fmt.Fprint(writer, endSequence); err != nil { errors <- err return } } type Errors chan error func (e Errors) GetError() error { select { case err := <-e: return err case <-time.After(time.Millisecond): } return nil } const operationSuccessful = 0 func checkOutput(reader io.Reader, errorChannel Errors) { writer := new(bytes.Buffer) io.Copy(writer, reader) if writer.Len() > 1 { data := writer.Bytes() if data[1] == operationSuccessful { return } else if len(data) > 2 { errorChannel <- errors.New(string(data[2:])) } } } //Upload uploads passed in content into remote destination func (c *service) Upload(destination string, mode os.FileMode, content []byte) (err error) { err = c.upload(destination, mode, content) if err != nil { if strings.Contains(err.Error(), "No such file or directory") { dir, _ := path.Split(destination) c.Run("mkdir -p " + dir) return c.upload(destination, mode, content) } else if strings.Contains(err.Error(), "handshake") || strings.Contains(err.Error(), "connection") { time.Sleep(500 * time.Millisecond) fmt.Printf("got error %v\n", err) c.Reconnect() return c.upload(destination, mode, content) } } return err } func (c *service) getSession() (*ssh.Session, error) { return c.client.NewSession() } //Upload uploads passed in content into remote destination func (c *service) upload(destination string, mode os.FileMode, content []byte) (err error) { dir, file := path.Split(destination) if mode == 0 { mode = 0644 } waitGroup := &sync.WaitGroup{} waitGroup.Add(1) if strings.HasPrefix(file, "/") { file = string(file[1:]) } session, err := c.getSession() if err != nil { return err } writer, err := session.StdinPipe() if err != nil { return errors.Wrap(err, "failed to acquire stdin") } defer writer.Close() var transferError Errors = make(chan error, 1) defer close(transferError) var sessionError Errors = make(chan error, 1) defer close(sessionError) output, err := session.StdoutPipe() if err != nil { return errors.Wrap(err, "failed to acquire stdout") } go checkOutput(output, sessionError) if mode >= 01000 { mode = storage.DefaultFileMode } fileMode := string(fmt.Sprintf("C%04o", mode)[:5]) createFileCmd := fmt.Sprintf("%v %d %s\n", fileMode, len(content), file) go c.transferData(content, createFileCmd, writer, transferError, waitGroup) scpCommand := "scp -qtr " + dir err = session.Start(scpCommand) if err != nil { return err } waitGroup.Wait() writerErr := writer.Close() if err := sessionError.GetError(); err != nil { return err } if err := transferError.GetError(); err != nil { return err } if err = session.Wait(); err != nil { if err := sessionError.GetError(); err != nil { return err } return err } return writerErr } //Download download passed source file from remote host. func (c *service) Download(source string) ([]byte, error) { session, err := c.client.NewSession() if err != nil { return nil, err } defer session.Close() return session.Output(fmt.Sprintf("cat %s", source)) } //Host returns client host func (c *service) Host() string { return c.host } //Close closes service func (c *service) Close() error { if len(c.forwarding) > 0 { for _, forwarding := range c.forwarding { _ = forwarding.Close() } } return c.client.Close() } //Reconnect client func (c *service) Reconnect() error { return c.connect() } //OpenTunnel tunnels data between localAddress and remoteAddress on ssh connection func (c *service) OpenTunnel(localAddress, remoteAddress string) error { local, err := net.Listen("tcp", localAddress) if err != nil { return errors.Wrap(err, fmt.Sprintf("failed to listen on local: %v %v", localAddress)) } var forwarding = NewForwarding(c.client, remoteAddress, local) if len(c.forwarding) == 0 { c.forwarding = make([]*Tunnel, 0) } c.forwarding = append(c.forwarding, forwarding) go forwarding.Handle() return nil } func (c *service) connect() (err error) { if c.client, err = ssh.Dial("tcp", c.host, c.config); err != nil { return errors.Wrap(err, fmt.Sprintf("failed to dial %v: %s", c.host)) } return nil } //NewService create a new ssh service, it takes host port and authentication config func NewService(host string, port int, authConfig *cred.Config) (Service, error) { if authConfig == nil { authConfig = &cred.Config{} } clientConfig, err := authConfig.ClientConfig() if err != nil { return nil, err } var result = &service{ host: fmt.Sprintf("%s:%d", host, port), config: clientConfig, } return result, result.connect() } toolbox-0.33.2/ssh/service_replay.go000066400000000000000000000036771374110251100174350ustar00rootroot00000000000000package ssh import ( "errors" "fmt" "golang.org/x/crypto/ssh" "os" ) //Service represents ssh service type replayService struct { storage map[string][]byte shellPrompt string system string commands *ReplayCommands } //Service returns a service wrapper func (s *replayService) Client() *ssh.Client { return &ssh.Client{} } //OpenMultiCommandSession opens multi command session func (s *replayService) OpenMultiCommandSession(config *SessionConfig) (MultiCommandSession, error) { return NewReplayMultiCommandSession(s.shellPrompt, s.system, s.commands), nil } //Run runs supplied command func (s *replayService) Run(command string) error { if commands, ok := s.commands.Commands[command]; ok { if commands.Error != "" { return errors.New(commands.Error) } } s.commands.Next(command) return nil } //Upload uploads provided content to specified destination func (s *replayService) Upload(destination string, mode os.FileMode, content []byte) error { s.storage[destination] = content return nil } //Download downloads content from specified source. func (s *replayService) Download(source string) ([]byte, error) { if _, has := s.storage[source]; !has { return nil, fmt.Errorf("no such file or directory") } return s.storage[source], nil } //OpenTunnel opens a tunnel between local to remote for network traffic. func (s *replayService) OpenTunnel(localAddress, remoteAddress string) error { return nil } func (s *replayService) NewSession() (*ssh.Session, error) { return &ssh.Session{}, nil } func (s *replayService) Reconnect() error { return errors.New("unsupported") } func (s *replayService) Close() error { return nil } func NewReplayService(shellPrompt, system string, commands *ReplayCommands, storage map[string][]byte) Service { if len(storage) == 0 { storage = make(map[string][]byte) } return &replayService{ storage: storage, shellPrompt: shellPrompt, system: system, commands: commands, } } toolbox-0.33.2/ssh/service_replay_test.go000066400000000000000000000016051374110251100204610ustar00rootroot00000000000000package ssh_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/ssh" "path" "testing" ) func Test_NewReplayService(t *testing.T) { parent := toolbox.CallerDirectory(3) commands, err := ssh.NewReplayCommands(path.Join(parent, "test/ls")) assert.Nil(t, err) err = commands.Load() if !assert.Nil(t, err) { return } assert.Equal(t, 3, len(commands.Commands)) assert.Nil(t, err) defer commands.Store() service := ssh.NewReplayService("AWITAS-C02TF066H040:awitas1511796457759720702$", "darwin", commands, nil) if err == nil { assert.NotNil(t, service) session, err := service.OpenMultiCommandSession(nil) assert.Nil(t, err) defer session.Close() assert.NotNil(t, session) var out string out, err = session.Run("ls /etc/hosts", nil, 2000) assert.Equal(t, "/etc/hosts", out) } else { assert.Nil(t, service) } } toolbox-0.33.2/ssh/service_test.go000066400000000000000000000043641374110251100171120ustar00rootroot00000000000000package ssh_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/ssh" "io/ioutil" "os" "path" "testing" ) func TestNewClient(t *testing.T) { commands, err := ssh.NewReplayCommands("/tmp/ls") if err != nil { return } defer commands.Store() service, err := ssh.NewService("127.0.0.1", 22, nil) if err != nil { return } assert.NotNil(t, service) commands.Enable(service) if err == nil { assert.NotNil(t, service) session, err := service.OpenMultiCommandSession(nil) assert.Nil(t, err) defer session.Close() assert.NotNil(t, session) var out string out, err = session.Run("ls /etc/hosts", nil, 2000) assert.Equal(t, "/etc/hosts", out) } else { assert.Nil(t, service) } } func TestClient_Upload(t *testing.T) { service, err := ssh.NewService("127.0.0.1", 22, nil) if err != nil { return } if err == nil { assert.NotNil(t, service) err = service.Upload("/tmp/a/abcd.bin", 0644, []byte{0x1, 0x6, 0x10}) assert.Nil(t, err) content, err := service.Download("/tmp/a/abcd.bin") assert.Nil(t, err) assert.Equal(t, []byte{0x1, 0x6, 0x10}, (content)) } else { assert.Nil(t, service) } } func TestClient_Key(t *testing.T) { auth, err := cred.NewConfig("/Users/awitas/.secret/scp1.json") if err != nil { return } assert.Nil(t, err) service, err := ssh.NewService("127.0.0.1", 22, auth) if err != nil { return } assert.NotNil(t, service) } func TestClient_UploadLargeFile(t *testing.T) { service, err := ssh.NewService("127.0.0.1", 22, nil) if err != nil { return } tempdir := os.TempDir() filename := path.Join(tempdir, "kkk/.bin/largefile.bin") toolbox.RemoveFileIfExist(filename) //defer toolbox.RemoveFileIfExist(filename) //file, err := os.OpenFile(filename, os.O_RDWR | os.O_CREATE, 0644) //assert.Nil(t, err) var payload = make([]byte, 1024*1024*16) for i := 0; i < len(payload); i += 32 { payload[i] = byte(i % 256) } //file.Write(payload) //file.Close() err = service.Upload(filename, 0644, payload) fmt.Printf("%v\n", err) assert.Nil(t, err) data, err := ioutil.ReadFile(filename) assert.Nil(t, err) if assert.Equal(t, len(payload), len(data)) { assert.EqualValues(t, data, payload) } } toolbox-0.33.2/ssh/session.go000066400000000000000000000271541374110251100161000ustar00rootroot00000000000000package ssh import ( "bytes" "fmt" "github.com/lunixbochs/vtclean" "github.com/pkg/errors" "github.com/viant/toolbox" "golang.org/x/crypto/ssh" "io" "path" "strings" "sync" "sync/atomic" "time" ) type TerminatedError struct { Err error } func (t *TerminatedError) Error() string { return fmt.Sprintf("terminated due to %v", t.Err) } //ErrTerminated - command session terminated var ErrTerminated = &TerminatedError{} const defaultShell = "/bin/bash" const ( drainTimeoutMs = 10 defaultTimeoutMs = 20000 stdoutFlashFrequencyMs = 1000 initTimeoutMs = 300 defaultTickFrequency = 100 ) //Listener represent command listener (it will send stdout fragments as thier being available on stdout) type Listener func(stdout string, hasMore bool) //MultiCommandSession represents a multi command session type MultiCommandSession interface { Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error) ShellPrompt() string System() string Reconnect() error Close() } //multiCommandSession represents a multi command session //a new command are send vi stdin type multiCommandSession struct { service *service config *SessionConfig replayCommands *ReplayCommands recordSession bool session *ssh.Session stdOutput chan string stdError chan string stdInput io.WriteCloser promptSequence string shellPrompt string escapedShellPrompt string system string running int32 stdin string } func (s *multiCommandSession) Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error) { if atomic.LoadInt32(&s.running) == 0 { return "", ErrTerminated } s.drainStdout() if !strings.HasSuffix(command, "\n") { command += "\n" } var stdin = command s.stdin = stdin _, err := s.stdInput.Write([]byte(stdin)) if err != nil { return "", fmt.Errorf("failed to execute command: %v, err: %v", command, err) } var output string output, _, err = s.readResponse(timeoutMs, listener, terminators...) if s.recordSession { s.replayCommands.Register(stdin, output) } return output, err } //ShellPrompt returns a shell prompt func (s *multiCommandSession) ShellPrompt() string { return s.shellPrompt } //System returns a system name func (s *multiCommandSession) System() string { return s.system } //Close closes the session with its resources func (s *multiCommandSession) Close() { atomic.StoreInt32(&s.running, 0) _ = s.stdInput.Close() if s.session != nil { _ = s.session.Close() } } func (s *multiCommandSession) closeIfError(err error) bool { if err != nil { if err != ErrTerminated { ErrTerminated.Err = err } s.Close() return true } return false } func (s *multiCommandSession) start(shell string) (output string, err error) { var reader, errReader io.Reader reader, err = s.session.StdoutPipe() if err != nil { return "", err } errReader, err = s.session.StderrPipe() if err != nil { return "", err } waitGroup := sync.WaitGroup{} waitGroup.Add(2) go func() { waitGroup.Done() s.copy(reader, s.stdOutput) }() go func() { waitGroup.Done() s.copy(errReader, s.stdError) }() if shell == "" { shell = defaultShell } waitGroup.Wait() s.stdin = shell err = s.session.Start(shell) if err != nil { return "", err } _, name := path.Split(shell) output, _, err = s.readResponse(defaultTimeoutMs, nil, name) return output, err } //copy copy data from reader to channel func (s *multiCommandSession) copy(reader io.Reader, out chan string) { var written int64 = 0 buf := make([]byte, 128*1024) var err error var bytesRead int for { writer := new(bytes.Buffer) if atomic.LoadInt32(&s.running) == 0 { return } bytesRead, err = reader.Read(buf) if bytesRead > 0 { bytesWritten, writeError := writer.Write(buf[:bytesRead]) if s.closeIfError(writeError) { return } if bytesWritten > 0 { written += int64(bytesWritten) } if bytesRead != bytesWritten { if s.closeIfError(io.ErrShortWrite) { return } } out <- string(writer.Bytes()) } if s.closeIfError(err) { return } } } func escapeInput(input string) string { input = vtclean.Clean(input, false) if input == "" { return input } return strings.Trim(input, "\n\r\t ") } func (s *multiCommandSession) Reconnect() (err error) { atomic.StoreInt32(&s.running, 1) s.service.Reconnect() s.session, err = s.service.client.NewSession() defer func() { if err != nil { s.service.client.Close() } }() if err != nil { return err } return s.init() } func (s *multiCommandSession) hasPrompt(input string) bool { escapedInput := escapeInput(input) var shellPrompt = s.shellPrompt if shellPrompt == "" { shellPrompt = "$" } if s.escapedShellPrompt == "" && s.shellPrompt != "" { s.escapedShellPrompt = escapeInput(s.shellPrompt) } if s.escapedShellPrompt != "" && strings.HasSuffix(escapedInput, s.escapedShellPrompt) || strings.HasSuffix(input, s.shellPrompt) { return true } return false } func (s *multiCommandSession) hasTerminator(input string, terminators ...string) bool { escapedInput := escapeInput(input) input = escapedInput for _, candidate := range terminators { candidateLen := len(candidate) if candidateLen == 0 { continue } if candidate[0:1] == "^" && strings.HasPrefix(input, candidate[1:]) { return true } if candidate[candidateLen-1:] == "$" && strings.HasSuffix(input, candidate[:candidateLen-1]) { return true } if strings.Contains(input, candidate) { return true } } return false } func (s *multiCommandSession) removePromptIfNeeded(stdout string) string { if strings.Contains(stdout, s.shellPrompt) { stdout = strings.Replace(stdout, s.shellPrompt, "", 1) var lines = []string{} for _, line := range strings.Split(stdout, "\n") { if strings.TrimSpace(line) == "" { continue } lines = append(lines, line) } stdout = strings.Join(lines, "\n") } return stdout } func (s *multiCommandSession) readResponse(timeoutMs int, listener Listener, terminators ...string) (out string, has bool, err error) { var hasPrompt, hasTerminator bool if timeoutMs == 0 { timeoutMs = defaultTimeoutMs } notification := newNotificationWindow(listener, stdoutFlashFrequencyMs) defer notification.flush() var done int32 defer atomic.StoreInt32(&done, 1) var errOut string var hasOutput bool var waitTimeMs = 0 var tickFrequencyMs = defaultTickFrequency if tickFrequencyMs > timeoutMs { tickFrequencyMs = timeoutMs } var timeoutDuration = time.Duration(tickFrequencyMs) * time.Millisecond outer: for { select { case partialOutput := <-s.stdOutput: waitTimeMs = 0 out += partialOutput hasTerminator = s.hasTerminator(out, terminators...) if len(partialOutput) > 0 { if hasTerminator { partialOutput = addLineBreakIfNeeded(partialOutput) } notification.notify(s.removePromptIfNeeded(partialOutput)) } hasPrompt = s.hasPrompt(out) if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 { break outer } case e := <-s.stdError: errOut += e notification.notify(s.removePromptIfNeeded(e)) hasPrompt = s.hasPrompt(errOut) hasTerminator = s.hasTerminator(errOut, terminators...) if (hasPrompt || hasTerminator) && len(s.stdOutput) == 0 { break outer } case <-time.After(timeoutDuration): waitTimeMs += tickFrequencyMs if waitTimeMs >= timeoutMs { break outer } } } if hasTerminator { s.drainStdout() } if errOut != "" { err = errors.New(errOut) } if len(out) > 0 { hasOutput = true out = s.removePromptIfNeeded(out) } return out, hasOutput, err } func addLineBreakIfNeeded(text string) string { index := strings.LastIndex(text, "\n") if index == -1 { return text + "\n" } lastFragment := string(text[index:]) if strings.TrimSpace(lastFragment) != "" { return text + "\n" } return text } func (s *multiCommandSession) drainStdout() { //read any outstanding output for { _, has, _ := s.readResponse(drainTimeoutMs, nil, "") if !has { return } } } func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { c := make(chan bool) go func() { defer close(c) wg.Wait() c <- true }() select { case <-c: return false // completed normally case <-time.After(timeout): return true // timed out } } func (s *multiCommandSession) shellInit() (err error) { if s.promptSequence != "" { if _, err = s.Run(s.promptSequence, nil, initTimeoutMs); err != nil { return err } } var ts = toolbox.AsString(time.Now().UnixNano()) var waitGroup = &sync.WaitGroup{} waitGroup.Add(1) if s.config.Shell == defaultShell { s.promptSequence = "PS1=\"" + ts + "\\$\"" s.shellPrompt = ts + "$" s.escapedShellPrompt = escapeInput(s.shellPrompt) } var listener Listener listener = func(stdout string, hasMore bool) { if !hasMore { waitGroup.Done() } } _, err = s.Run("", listener, initTimeoutMs, "$") waitTimeout(waitGroup, 60*time.Second) s.drainStdout() _, err = s.Run(s.promptSequence, nil, defaultTimeoutMs, "$") if s.closeIfError(err) { return err } for i := 0; i < 3; i++ { s.system, err = s.Run("uname -s", nil, initTimeoutMs) s.system = strings.ToLower(strings.TrimSpace(s.system)) if strings.TrimSpace(s.system) != "" && !strings.Contains(s.system, "$") { break } } s.drainStdout() return nil } func (s *multiCommandSession) init() (err error) { s.session, err = s.service.client.NewSession() defer func() { if err != nil { s.service.client.Close() } }() s.stdOutput = make(chan string) s.stdError = make(chan string) for k, v := range s.config.EnvVariables { err = s.session.Setenv(k, v) if err != nil { return err } } modes := ssh.TerminalModes{ ssh.ECHO: 0, // disable echoing ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud } if err := s.session.RequestPty(s.config.Term, s.config.Rows, s.config.Columns, modes); err != nil { return err } if s.stdInput, err = s.session.StdinPipe(); err != nil { return err } stdout, err := s.start(s.config.Shell) if s.closeIfError(err) { return err } if err = checkNotFound(stdout); err != nil { return fmt.Errorf("failed to open %v shell, %v", s.config.Shell, err) } return s.shellInit() } func checkNotFound(output string) error { if strings.Contains(output, "not found") { return fmt.Errorf("failed run %s", output) } return nil } func newMultiCommandSession(service *service, config *SessionConfig, replayCommands *ReplayCommands, recordSession bool) (MultiCommandSession, error) { if config == nil { config = &SessionConfig{} } config.applyDefault() result := &multiCommandSession{ service: service, config: config, running: 1, recordSession: recordSession, replayCommands: replayCommands, } return result, result.init() } type notificationWindow struct { checkpoint *time.Time listener Listener elapsedMs int stdout string frequencyMs int } func (t *notificationWindow) flush() { if t.listener == nil { return } if t.stdout != "" { t.listener(t.stdout, true) } t.listener("", false) } func (t *notificationWindow) notify(stdout string) { var now = time.Now() if t.listener == nil { return } t.stdout += stdout t.elapsedMs += int(now.Sub(*t.checkpoint) / time.Millisecond) t.checkpoint = &now if t.elapsedMs > t.frequencyMs { t.listener(t.stdout, true) t.stdout = "" t.elapsedMs = 0 } } func newNotificationWindow(listener Listener, frequencyMs int) *notificationWindow { var now = time.Now() return ¬ificationWindow{ checkpoint: &now, listener: listener, frequencyMs: frequencyMs, } } toolbox-0.33.2/ssh/session_config.go000066400000000000000000000006331374110251100174160ustar00rootroot00000000000000package ssh //SessionConfig represents a new session config type SessionConfig struct { EnvVariables map[string]string Shell string Term string Rows int Columns int } func (c *SessionConfig) applyDefault() { if c.Shell == "" { c.Shell = "/bin/bash" } if c.Term == "" { c.Term = "xterm" } if c.Rows == 0 { c.Rows = 100 } if c.Columns == 0 { c.Columns = 100 } } toolbox-0.33.2/ssh/session_replay.go000066400000000000000000000021431374110251100174430ustar00rootroot00000000000000package ssh import ( "errors" "strings" ) const commandNotFound = "Command not found" type replayMultiCommandSession struct { shellPrompt string system string replay *ReplayCommands } func (s *replayMultiCommandSession) Run(command string, listener Listener, timeoutMs int, terminators ...string) (string, error) { if !strings.HasSuffix(command, "\n") { command = command + "\n" } replay, ok := s.replay.Commands[command] if !ok { return commandNotFound, nil } if replay.Error != "" { return "", errors.New(replay.Error) } return s.replay.Next(command), nil } func (s *replayMultiCommandSession) Reconnect() error { return errors.New("unsupported") } func (s *replayMultiCommandSession) ShellPrompt() string { return s.shellPrompt } func (s *replayMultiCommandSession) System() string { return s.system } func (s *replayMultiCommandSession) Close() { } func NewReplayMultiCommandSession(shellPrompt, system string, commands *ReplayCommands) MultiCommandSession { return &replayMultiCommandSession{ shellPrompt: shellPrompt, system: system, replay: commands, } } toolbox-0.33.2/ssh/test/000077500000000000000000000000001374110251100150345ustar00rootroot00000000000000toolbox-0.33.2/ssh/test/ls/000077500000000000000000000000001374110251100154525ustar00rootroot00000000000000toolbox-0.33.2/ssh/test/ls/001_000.stdin000066400000000000000000000000111374110251100173640ustar00rootroot00000000000000uname -s toolbox-0.33.2/ssh/test/ls/001_001.stdout000066400000000000000000000000061374110251100175720ustar00rootroot00000000000000Darwintoolbox-0.33.2/ssh/test/ls/002_000.stdin000066400000000000000000000000161374110251100173720ustar00rootroot00000000000000ls /etc/hosts toolbox-0.33.2/ssh/test/ls/002_001.stdout000066400000000000000000000000121374110251100175700ustar00rootroot00000000000000/etc/hoststoolbox-0.33.2/ssh/test/ls/003_000.stdin000066400000000000000000000000411374110251100173710ustar00rootroot00000000000000PS1="\h:\u1511796457759720702\$" toolbox-0.33.2/ssh/test/ls/003_001.stdout000066400000000000000000000000561374110251100176010ustar00rootroot00000000000000AWITAS-C02TF066H040:awitas1511796457759720702$toolbox-0.33.2/ssh/tunnel.go000066400000000000000000000035271374110251100157200ustar00rootroot00000000000000package ssh import ( "fmt" "golang.org/x/crypto/ssh" "io" "log" "net" "sync" "sync/atomic" ) //Tunnel represents a SSH forwarding link type Tunnel struct { RemoteAddress string client *ssh.Client Local net.Listener Connections []net.Conn mutex *sync.Mutex closed int32 } func (f *Tunnel) tunnelTraffic(local, remote net.Conn) { defer local.Close() defer remote.Close() completionChannel := make(chan bool) go func() { _, err := io.Copy(local, remote) if err != nil { log.Printf("failed to copy remote to local: %v", err) } completionChannel <- true }() go func() { _, _ = io.Copy(remote, local) //if err != nil { // log.Printf("failed to copy local to remote: %v", err) //} completionChannel <- true }() <-completionChannel } //Handle listen on local client to create tunnel with remote address. func (f *Tunnel) Handle() error { for { if atomic.LoadInt32(&f.closed) == 1 { return nil } localclient, err := f.Local.Accept() if err != nil { return err } remote, err := f.client.Dial("tcp", f.RemoteAddress) if err != nil { return fmt.Errorf("failed to connect to remote: %v %v", f.RemoteAddress, err) } f.Connections = append(f.Connections, remote) f.Connections = append(f.Connections, localclient) go f.tunnelTraffic(localclient, remote) } return nil } //Close closes forwarding link func (f *Tunnel) Close() error { atomic.StoreInt32(&f.closed, 1) _ = f.Local.Close() for _, remote := range f.Connections { _ = remote.Close() } return nil } //NewForwarding creates a new ssh forwarding link func NewForwarding(client *ssh.Client, remoteAddress string, local net.Listener) *Tunnel { return &Tunnel{ client: client, RemoteAddress: remoteAddress, Connections: make([]net.Conn, 0), Local: local, mutex: &sync.Mutex{}, } } toolbox-0.33.2/stack_helper.go000066400000000000000000000030461374110251100162560ustar00rootroot00000000000000package toolbox import ( "path" "runtime" "strings" ) // CallerInfo return filename, function or file line from the stack func CallerInfo(callerIndex int) (string, string, int) { var callerPointer = make([]uintptr, 10) // at least 1 entry needed runtime.Callers(callerIndex, callerPointer) callerInfo := runtime.FuncForPC(callerPointer[0]) file, line := callerInfo.FileLine(callerPointer[0]) callerName := callerInfo.Name() dotPosition := strings.LastIndex(callerName, ".") return file, callerName[dotPosition+1:], line } //CallerDirectory returns directory of caller source code directory func CallerDirectory(callerIndex int) string { file, _, _ := CallerInfo(callerIndex) parent, _ := path.Split(file) return parent } func hasMatch(target string, candidates ...string) bool { for _, candidate := range candidates { if strings.HasSuffix(target, candidate) { return true } } return false } //DiscoverCaller returns the first matched caller info func DiscoverCaller(offset, maxDepth int, ignoreFiles ...string) (string, string, int) { var callerPointer = make([]uintptr, maxDepth) // at least 1 entry needed var caller *runtime.Func var filename string var line int for i := offset; i < maxDepth; i++ { runtime.Callers(i, callerPointer) caller = runtime.FuncForPC(callerPointer[0]) filename, line = caller.FileLine(callerPointer[0]) if hasMatch(filename, ignoreFiles...) { continue } break } callerName := caller.Name() dotPosition := strings.LastIndex(callerName, ".") return filename, callerName[dotPosition+1:], line } toolbox-0.33.2/storage/000077500000000000000000000000001374110251100147245ustar00rootroot00000000000000toolbox-0.33.2/storage/README.md000066400000000000000000000045261374110251100162120ustar00rootroot00000000000000## Storage API Deprecated - please use https://github.com/viant/afs API instead This API provides unified way of accessing any storage system. It comes with the following implementation so far: 0 { result = string(result[pathRoot:]) } if strings.HasSuffix(result, "/") { result = string(result[:len(result)-1]) } return result } func truncatePath(path string) string { if len(path) <= 1 { return path } if strings.HasSuffix(path, "/") { return string(path[:len(path)-1]) } return path } func copyStorageContent(sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, subPath string, copyHandler CopyHandler) error { sourceListURL := sourceURL if subPath != "" { sourceListURL = toolbox.URLPathJoin(sourceURL, subPath) } objects, err := sourceService.List(sourceListURL) if err != nil { return err } for _, object := range objects { if err = copyObject(object, sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, subPath, copyHandler); err != nil { return err } } return nil } func copyObject(object Object, sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, subPath string, copyHandler CopyHandler) error { var objectRelativePath string sourceURLPath := urlPath(sourceURL) var objectURLPath = urlPath(object.URL()) if object.IsFolder() { if truncatePath(sourceURLPath) == truncatePath(objectURLPath) { return nil } if subPath != "" && objectURLPath == toolbox.URLPathJoin(sourceURLPath, subPath) { return nil } } if len(objectURLPath) > len(sourceURLPath) { objectRelativePath = objectURLPath[len(sourceURLPath):] if strings.HasPrefix(objectRelativePath, "/") { objectRelativePath = string(objectRelativePath[1:]) } } var destinationObjectURL = destinationURL if objectRelativePath != "" { destinationObjectURL = toolbox.URLPathJoin(destinationURL, objectRelativePath) } if object.IsContent() { reader, err := sourceService.Download(object) if err != nil { err = fmt.Errorf("unable download, %v -> %v, %v", object.URL(), destinationObjectURL, err) return err } defer reader.Close() if modifyContentHandler != nil { content, err := ioutil.ReadAll(reader) if err != nil { return err } reader = ioutil.NopCloser(bytes.NewReader(content)) reader, err = modifyContentHandler(reader) if err != nil { err = fmt.Errorf("unable modify content, %v %v %v", object.URL(), destinationObjectURL, err) return err } } if subPath == "" { _, sourceName := path.Split(object.URL()) _, destinationName := path.Split(destinationURL) if strings.HasSuffix(destinationObjectURL, "/") { destinationObjectURL = toolbox.URLPathJoin(destinationObjectURL, sourceName) } else { destinationObject, _ := destinationService.StorageObject(destinationObjectURL) if destinationObject != nil && destinationObject.IsFolder() { destinationObjectURL = toolbox.URLPathJoin(destinationObjectURL, sourceName) } else if destinationName != sourceName { if !strings.Contains(destinationName, ".") { destinationObjectURL = toolbox.URLPathJoin(destinationURL, sourceName) } } } } err = copyHandler(object, reader, destinationService, destinationObjectURL) if err != nil { return err } } else { if err := copyStorageContent(sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, objectRelativePath, copyHandler); err != nil { return err } } return nil } func copySourceToDestination(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { mode := DefaultFileMode if fileInfo := sourceObject.FileInfo(); fileInfo != nil { mode = fileInfo.Mode() } err := destinationService.UploadWithMode(destinationURL, mode, reader) if err != nil { err = fmt.Errorf("unable upload, %v %v %v", sourceObject.URL(), destinationURL, err) } return err } func getArchiveCopyHandler(archive *zip.Writer, parentURL string) CopyHandler { return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { var _, relativePath = toolbox.URLSplit(destinationURL) if destinationURL != parentURL { relativePath = strings.Replace(destinationURL, parentURL, "", 1) } header, err := zip.FileInfoHeader(sourceObject.FileInfo()) if err != nil { return err } header.Method = zip.Deflate header.Name = relativePath writer, err := archive.CreateHeader(header) if err != nil { return err } _, err = io.Copy(writer, reader) return err } } //Copy downloads objects from source URL to upload them to destination URL. func Copy(sourceService Service, sourceURL string, destinationService Service, destinationURL string, modifyContentHandler ModificationHandler, copyHandler CopyHandler) (err error) { if copyHandler == nil { copyHandler = copySourceToDestination } if strings.HasSuffix(sourceURL, "//") { sourceURL = string(sourceURL[:len(sourceURL)-1]) } err = copyStorageContent(sourceService, sourceURL, destinationService, destinationURL, modifyContentHandler, "", copyHandler) if err != nil { err = fmt.Errorf("failed to copy %v -> %v: %v", sourceURL, destinationURL, err) } return err } //Archive archives supplied URL assets into zip writer func Archive(service Service, URL string, writer *zip.Writer) error { memService := NewMemoryService() var destURL = "mem:///dev/nul" return Copy(service, URL, memService, destURL, nil, getArchiveCopyHandler(writer, destURL)) } func getArchiveCopyHandlerWithFilter(archive *zip.Writer, parentURL string, predicate func(candidate Object) bool) CopyHandler { return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { if !predicate(sourceObject) { return nil } var _, relativePath = toolbox.URLSplit(destinationURL) if destinationURL != parentURL { relativePath = strings.Replace(destinationURL, parentURL, "", 1) } header, err := zip.FileInfoHeader(sourceObject.FileInfo()) if err != nil { return err } header.Method = zip.Store if strings.HasPrefix(relativePath, "/") { relativePath = string(relativePath[1:]) } header.Name = relativePath writer, err := archive.CreateHeader(header) if err != nil { return err } _, err = io.Copy(writer, reader) return err } } //Archive archives supplied URL assets into zip writer with supplied filter func ArchiveWithFilter(service Service, URL string, writer *zip.Writer, predicate func(candidate Object) bool) error { memService := NewMemoryService() var destURL = "mem:///dev/nul" return Copy(service, URL, memService, destURL, nil, getArchiveCopyHandlerWithFilter(writer, destURL, predicate)) } func getTarCopyHandler(archive *tar.Writer, destParentURL, parentURL string, dirs map[string]bool) CopyHandler { if strings.HasSuffix(parentURL, "/") { parentURL = string(parentURL[:len(parentURL)-2]) } _, root := path.Split(destParentURL) if root == "." { root = "" } return func(sourceObject Object, reader io.Reader, destinationService Service, destinationURL string) error { var _, relativePath = toolbox.URLSplit(destinationURL) if destinationURL != parentURL { relativePath = strings.Replace(destinationURL, parentURL, "", 1) } if strings.HasPrefix(relativePath, "/") { relativePath = string(relativePath[1:]) } relativePath = path.Join(root, relativePath) parent, _ := path.Split(relativePath) if parent != "" && !dirs[parent] { tarHeader := &tar.Header{ Name: parent, Size: int64(0), Mode: int64(sourceObject.FileInfo().Mode()), ModTime: sourceObject.FileInfo().ModTime(), } if err := archive.WriteHeader(tarHeader); err != nil { return fmt.Errorf(" unable to write tar header, %v", err) } dirs[parent] = true } contents := new(bytes.Buffer) if _, err := io.Copy(contents, reader); err != nil { return err } data := contents.Bytes() tarHeader := &tar.Header{ Name: relativePath, Size: int64(len(data)), Mode: int64(sourceObject.FileInfo().Mode()), ModTime: sourceObject.FileInfo().ModTime(), } if err := archive.WriteHeader(tarHeader); err != nil { return fmt.Errorf(" unable to write tar header, %v", err) } if _, err := archive.Write(data); err != nil { return fmt.Errorf(" unable to write tar content, %v", err) } return nil } } //Tar tar archives supplied URL assets into zip writer func Tar(service Service, URL string, writer *tar.Writer, includeOwnerDir bool) error { memService := NewMemoryService() var destURL = "mem:///dev/nul" var dirs = make(map[string]bool) ownerDir := "" if includeOwnerDir { ownerDir = URL } return Copy(service, URL, memService, destURL, nil, getTarCopyHandler(writer, ownerDir, destURL, dirs)) } toolbox-0.33.2/storage/copy_test.go000066400000000000000000000032201374110251100172610ustar00rootroot00000000000000package storage_test import ( "archive/zip" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" _ "github.com/viant/toolbox/storage/scp" "os" "path" "strings" "testing" ) func TestCopy(t *testing.T) { service := storage.NewService() assert.NotNil(t, service) parent := toolbox.CallerDirectory(3) baseUrl := "file://" + parent + "/test" toolbox.CreateDirIfNotExist(path.Join(parent, "test/target")) { sourceURL := path.Join(baseUrl, "source/") targetURL := path.Join(baseUrl, "target/") err := storage.Copy(service, sourceURL, service, targetURL, nil, nil) assert.Nil(t, err) expectedFiles := []string{ path.Join(parent, "test/target/file1.txt"), path.Join(parent, "test/target/file2.txt"), path.Join(parent, "test/target/dir/file.json"), path.Join(parent, "test/target/dir2/subdir/file1.txt"), } for _, file := range expectedFiles { assert.True(t, toolbox.FileExists(file)) //os.Remove(file) } } } func TestArchive(t *testing.T) { memService := storage.NewMemoryService() memService.Upload("mem://test/copy/archive/file1.txt", strings.NewReader("abc")) memService.Upload("mem://test/copy/archive/file2.txt", strings.NewReader("xyz")) memService.Upload("mem://test/copy/archive/config/test.prop", strings.NewReader("123")) toolbox.RemoveFileIfExist("/tmp/testCopy.zip") var writer, err = os.OpenFile("/tmp/testCopy.zip", os.O_CREATE|os.O_WRONLY, 06444) if assert.Nil(t, err) { defer writer.Close() archive := zip.NewWriter(writer) err = storage.Archive(memService, "mem://test/copy/archive/", archive) assert.Nil(t, err) archive.Flush() archive.Close() } } toolbox-0.33.2/storage/file_info.go000066400000000000000000000024501374110251100172060ustar00rootroot00000000000000package storage import ( "fmt" "os" "strings" "time" ) type fileInfo struct { name string size int64 mode os.FileMode modTime time.Time isDir bool } func (i *fileInfo) Name() string { return i.name } func (i *fileInfo) Size() int64 { return i.size } func (i *fileInfo) Mode() os.FileMode { return i.mode } func (i *fileInfo) ModTime() time.Time { return i.modTime } func (i *fileInfo) IsDir() bool { return i.isDir } func (i *fileInfo) Sys() interface{} { return i } func NewFileInfo(name string, size int64, mode os.FileMode, modificationTime time.Time, isDir bool) os.FileInfo { return &fileInfo{ name: name, size: size, mode: mode, modTime: modificationTime, isDir: isDir, } } func NewFileMode(fileAttributes string) (os.FileMode, error) { var result os.FileMode if len(fileAttributes) != 10 { return result, fmt.Errorf("Invalid attribute length %v %v", fileAttributes, len(fileAttributes)) } const fileType = "dalTLDpSugct" var fileModePosition = strings.Index(fileType, string(fileAttributes[0])) if fileModePosition != -1 { result = 1 << uint(32-1-fileModePosition) } const filePermission = "rwxrwxrwx" for i, c := range filePermission { if c == rune(fileAttributes[i+1]) { result = result | 1< 0 { query.Prefix = parsedUrl.Path[1:] } responseIterator := client.Bucket(parsedUrl.Host).Objects(ctx, query) var result = make([]tstorage.Object, 0) for obj, err := responseIterator.Next(); err == nil; obj, err = responseIterator.Next() { objectURL := "gs://" + parsedUrl.Host + "/" + obj.Prefix + obj.Name var fileMode, _ = tstorage.NewFileMode("-rw-rw-rw-") if obj.Prefix != "" { fileMode, _ = tstorage.NewFileMode("drw-rw-rw-") } var _, name = toolbox.URLSplit(objectURL) var fileInfo = tstorage.NewFileInfo(name, obj.Size, fileMode, obj.Updated, fileMode.IsDir()) var object = newStorageObject(objectURL, obj, fileInfo) result = append(result, object) } return result, err } func (s *service) Exists(URL string) (bool, error) { objects, err := s.List(URL) if err != nil { return false, err } return len(objects) > 0, nil } func (s *service) StorageObject(URL string) (tstorage.Object, error) { objects, err := s.List(URL) if err != nil { return nil, err } if len(objects) == 0 { return nil, fmt.Errorf("Not found %v", URL) } return objects[0], nil } //Download returns reader for downloaded storage object func (s *service) Download(object tstorage.Object) (io.ReadCloser, error) { client, ctx, err := s.NewClient() if err != nil { return nil, err } defer client.Close() objectInfo := &storage.ObjectAttrs{} err = object.Unwrap(&objectInfo) if err != nil { return nil, err } return client.Bucket(objectInfo.Bucket). Object(objectInfo.Name). NewReader(ctx) } func (s *service) Upload(URL string, reader io.Reader) error { return s.UploadWithMode(URL, tstorage.DefaultFileMode, reader) } func (s *service) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { parserURL, err := url.Parse(URL) if err != nil { return fmt.Errorf("failed to parse URL for uploading: %v, %v", URL, err) } client, ctx, err := s.NewClient() if err != nil { return err } defer client.Close() name := parserURL.Path if len(parserURL.Path) > 0 { name = parserURL.Path[1:] } err = s.uploadContent(ctx, client, parserURL, name, reader) if toolbox.IsNotFoundError(err) { err := client.Bucket(parserURL.Host).Create(ctx, s.projectID, &storage.BucketAttrs{}) if err != nil { return fmt.Errorf("failed to create bucket: %v, %v", parserURL.Host, err) } //_, _ = client.Bucket(parserURL.Host).DefaultObjectACL().List(ctx) return s.uploadContent(ctx, client, parserURL, name, reader) } if err != nil { return fmt.Errorf("unable upload: %v", err) } return nil } func (s *service) uploadContent(ctx context.Context, client *storage.Client, parserURL *url.URL, name string, reader io.Reader) error { writer := client.Bucket(parserURL.Host). Object(name). NewWriter(ctx) expiry := parserURL.Query().Get("expiry") if expiry != "" { writer.Metadata = map[string]string{ "Cache-Control": "private, max-age=" + expiry, } } reader, err := updateUploadChecksum(parserURL, reader, writer) if _, err = io.Copy(writer, reader); err != nil { return fmt.Errorf("failed to copy to writer during upload:%v", err) } if err = writer.Close(); err != nil { return toolbox.ReclassifyNotFoundIfMatched(err, parserURL.String()) } return nil } func updateUploadChecksum(parserURL *url.URL, reader io.Reader, writer *storage.Writer) (io.Reader, error) { checksumDisabled := parserURL.Query().Get("disableChecksum") != "" updateMD5 := parserURL.Query().Get("disableMD5") == "" updateCRC := parserURL.Query().Get("disableCRC32") == "" if !(updateCRC || updateMD5) || checksumDisabled { return reader, nil } var err error bufferReader, ok := reader.(*bytes.Buffer) if !ok { content, err := ioutil.ReadAll(reader) if err != nil { return nil, fmt.Errorf("failed to read all during upload:%v", err) } bufferReader = bytes.NewBuffer(content) } if parserURL.Query().Get("disableMD5") == "" { hashReader := bytes.NewBuffer(bufferReader.Bytes()) h := md5.New() _, _ = io.Copy(h, hashReader) writer.MD5 = h.Sum(nil) hashReader.Reset() } else if parserURL.Query().Get("disableCRC32") == "" { crc32HashReader := bytes.NewBuffer(bufferReader.Bytes()) crc32Hash := crc32.New(crc32.MakeTable(crc32.Castagnoli)) _, _ = io.Copy(crc32Hash, crc32HashReader) writer.CRC32C = crc32Hash.Sum32() crc32HashReader.Reset() } return bufferReader, err } func (s *service) Register(schema string, service tstorage.Service) error { return errors.New("unsupported") } func (s *service) Close() error { return nil } func (s *service) listAll(URL string, result *[]tstorage.Object) error { if !strings.HasSuffix(URL, "/") { URL += "/" } objects, err := s.List(URL) if err != nil { return err } for _, object := range objects { if !object.IsFolder() { *result = append(*result, object) continue } if err = s.listAll(object.URL(), result); err != nil { return err } } return nil } //Delete removes passed in storage object func (s *service) Delete(object tstorage.Object) error { client, ctx, err := s.NewClient() if err != nil { return err } defer client.Close() objectInfo := &storage.ObjectAttrs{} err = object.Unwrap(&objectInfo) if err != nil { return err } if object.IsFolder() { var objects = []tstorage.Object{} err := s.listAll(object.URL(), &objects) if err != nil { return err } for _, object := range objects { if err := s.Delete(object); err != nil { return err } } return nil } return client.Bucket(objectInfo.Bucket). Object(objectInfo.Name).Delete(ctx) } //DownloadWithURL downloads content for passed in object URL func (s *service) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } //NewService create a new gc storage service func NewService(projectId string, options ...option.ClientOption) *service { return &service{ projectID: projectId, options: options, } } toolbox-0.33.2/storage/gs/service_test.go000066400000000000000000000007701374110251100203670ustar00rootroot00000000000000package gs import ( "testing" ) func TestService_List(t *testing.T) { /* credential := option.WithServiceAccountFile("") service := gs.NewService(credential) assert.NotNil(t, service) objects, err := service.List("") assert.Nil(t, err) for _, o := range objects { fmt.Printf("%v\n", o.URL()) } object, err := service.StorageObject("") assert.Nil(t, err) assert.NotNil(t, object) err = service.Delete(object) assert.Nil(t, err)*/ } toolbox-0.33.2/storage/http_service.go000066400000000000000000000176141374110251100177630ustar00rootroot00000000000000package storage import ( "fmt" "github.com/pkg/errors" "github.com/viant/toolbox" "github.com/viant/toolbox/cred" "io" "io/ioutil" "net/http" "net/url" "os" "path" "path/filepath" "strings" "time" ) //httpStorageService represents basic http storage service (only limited listing and full download are supported) type httpStorageService struct { Credential *cred.Config } //HTTPClientProvider represents http client provider var HTTPClientProvider = func() (*http.Client, error) { return toolbox.NewHttpClient(&toolbox.HttpOptions{Key: "MaxIdleConns", Value: 0}) } func (s *httpStorageService) addCredentialToURLIfNeeded(URL string) string { if s.Credential == nil || s.Credential.Password == "" || s.Credential.Username == "" { return URL } prasedURL, err := url.Parse(URL) if err != nil { return URL } if prasedURL.User != nil { return URL } return strings.Replace(URL, "://", fmt.Sprintf("://%v:%v@", s.Credential.Username, s.Credential.Password), 1) } type hRef struct { URL string Value string } func extractLinks(body string) []*hRef { var result = make([]*hRef, 0) var linkContents = strings.Split(string(body), "href=\"") for i := 1; i < len(linkContents); i++ { var linkContent = linkContents[i] linkEndPosition := strings.Index(linkContent, "\"") if linkEndPosition == -1 { continue } linkHref := string(linkContent[:linkEndPosition]) var content = "" contentStartPosition := strings.Index(linkContent, ">") if contentStartPosition != 1 { content = string(linkContent[contentStartPosition+1:]) contentEndPosition := strings.Index(content, "<") if contentEndPosition != -1 { content = string(content[:contentEndPosition]) } } link := &hRef{ URL: linkHref, Value: strings.Trim(content, " \t\r\n"), } result = append(result, link) } return result } //List returns a list of object for supplied url func (s *httpStorageService) List(URL string) ([]Object, error) { listURL := s.addCredentialToURLIfNeeded(URL) client, err := HTTPClientProvider() if err != nil { return nil, err } response, err := client.Get(listURL) if err != nil { return nil, err } body, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } now := time.Now() contentType := response.Header.Get("Content-Type") var result = make([]Object, 0) if response.Status != "200 OK" { return nil, fmt.Errorf("Invalid response code: %v", response.Status) } isGitUrl := strings.Contains(URL, "github.") isPublicGit := strings.Contains(URL, "github.com") if strings.Contains(contentType, "text/html") { links := extractLinks(string(body)) var indexedLinks = map[string]bool{} if isGitUrl { for _, link := range links { if !((strings.Contains(link.URL, "/blob/") || strings.Contains(link.URL, "/tree/")) && strings.HasSuffix(link.URL, link.Value)) { continue } linkType := StorageObjectContentType _, name := toolbox.URLSplit(link.URL) if path.Ext(name) == "" { linkType = StorageObjectFolderType } baseURL := toolbox.URLBase(URL) objectURL := link.URL if !strings.Contains(objectURL, baseURL) { objectURL = toolbox.URLPathJoin(baseURL, link.URL) } if linkType == StorageObjectContentType && strings.Contains(objectURL, "/master/") { objectURL = strings.Replace(objectURL, "/blob/", "/", 1) if isPublicGit { objectURL = strings.Replace(objectURL, "github.com", "raw.githubusercontent.com", 1) } else { objectURL = strings.Replace(objectURL, ".com/", ".com/raw/", 1) } } if linkType == StorageObjectContentType && !strings.Contains(objectURL, "raw") { continue } if _, ok := indexedLinks[objectURL]; ok { continue } storageObject := newHttpFileObject(objectURL, linkType, nil, now, 1) indexedLinks[objectURL] = true result = append(result, storageObject) } } else { for _, link := range links { if link.URL == "" || strings.Contains(link.URL, ":") || strings.HasPrefix(link.URL, "#") || strings.HasPrefix(link.URL, "?") || strings.HasPrefix(link.URL, ".") || strings.HasPrefix(link.URL, "/") { continue } linkType := StorageObjectContentType if strings.HasSuffix(link.URL, "/") { linkType = StorageObjectFolderType } objectURL := toolbox.URLPathJoin(URL, link.URL) storageObject := newHttpFileObject(objectURL, linkType, nil, now, 1) result = append(result, storageObject) } } } if strings.Contains(string(body), ">..<") { return result, err } storageObject := newHttpFileObject(URL, StorageObjectContentType, nil, now, response.ContentLength) result = append(result, storageObject) return result, err } //Exists returns true if resource exists func (s *httpStorageService) Exists(URL string) (bool, error) { client, err := HTTPClientProvider() if err != nil { return false, err } response, err := client.Get(URL) if err != nil { return false, err } return response.StatusCode == 200, nil } //Object returns a Object for supplied url func (s *httpStorageService) StorageObject(URL string) (Object, error) { objects, err := s.List(URL) if err != nil { return nil, err } if len(objects) == 0 { return nil, fmt.Errorf("resource not found: %v", URL) } return objects[0], nil } //Download returns reader for downloaded storage object func (s *httpStorageService) Download(object Object) (io.ReadCloser, error) { client, err := HTTPClientProvider() if err != nil { return nil, err } response, err := client.Get(s.addCredentialToURLIfNeeded(object.URL())) return response.Body, err } //Upload uploads provided reader content for supplied url. func (s *httpStorageService) Upload(URL string, reader io.Reader) error { return errors.New("unsupported") } //Upload uploads provided reader content for supplied url. func (s *httpStorageService) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { return errors.New("unsupported") } func (s *httpStorageService) Register(schema string, service Service) error { return errors.New("unsupported") } //Delete removes passed in storage object func (s *httpStorageService) Delete(object Object) error { fileName := toolbox.Filename(object.URL()) return os.Remove(fileName) } func (s *httpStorageService) Close() error { return nil } //DownloadWithURL downloads content for passed in object URL func (s *httpStorageService) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } func NewHttpStorageService(credential *cred.Config) Service { return &httpStorageService{ Credential: credential, } } type httpStorageObject struct { *AbstractObject } func (o *httpStorageObject) Unwrap(target interface{}) error { return fmt.Errorf("unsuported target %T", target) } func newHttpFileObject(url string, objectType int, source interface{}, lastModified time.Time, size int64) Object { var isDir = objectType == StorageObjectFolderType var _, name = toolbox.URLSplit(url) var fileMode, _ = NewFileMode("-r--r--r--") if isDir { fileMode, _ = NewFileMode("dr--r--r--") } fileInfo := NewFileInfo(name, size, fileMode, lastModified, isDir) abstract := NewAbstractStorageObject(url, source, fileInfo) result := &httpStorageObject{ AbstractObject: abstract, } result.AbstractObject.Object = result return result } const HttpProviderScheme = "http" const HttpsProviderScheme = "https" func init() { Registry().Registry[HttpsProviderScheme] = httpServiceProvider Registry().Registry[HttpProviderScheme] = httpServiceProvider } func httpServiceProvider(credentialFile string) (Service, error) { if credentialFile == "" { return NewHttpStorageService(nil), nil } if !strings.HasPrefix(credentialFile, "/") { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) credentialFile = path.Join(dir, credentialFile) } config, err := cred.NewConfig(credentialFile) if err != nil { return nil, err } return NewHttpStorageService(config), nil } toolbox-0.33.2/storage/http_service_test.go000066400000000000000000000011341374110251100210100ustar00rootroot00000000000000package storage_test import "testing" func TestNewHttpStorageService(t *testing.T) { // Deprecated use gith ub/viant/afs/http //{ // service, err := storage.NewServiceForURL("https://github.com/viant/", credentialFile) // assert.Nil(t, err) // assert.NotNil(t, service) // // objects, err := service.List("https://github.com/viant/") // assert.True(t, len(objects) > 0) // reader, err := service.Download(objects[0]) // if assert.Nil(t, err) { // defer reader.Close() // } // content, err := ioutil.ReadAll(reader) // assert.Nil(t, err) // assert.True(t, len(content) > 0) // //} } toolbox-0.33.2/storage/mapper.go000066400000000000000000000066151374110251100165470ustar00rootroot00000000000000package storage import ( "fmt" "github.com/viant/toolbox" "io" "io/ioutil" "os" "path" "strings" ) type StorageMapping struct { SourceURL string SourceCredential string DestinationURI string TargetFile string TargetPackage string UseTextFormat bool } var binaryFormats = map[string]bool{ ".png": true, ".jpeg": true, ".ico": true, ".jpg": true, } //GenerateStorageCode create a *.go files with statically scanned content from source URL. func GenerateStorageCode(mappings ...*StorageMapping) error { destinationService := NewMemoryService() for _, mapping := range mappings { sourceService, err := NewServiceForURL(mapping.SourceURL, mapping.SourceCredential) if err != nil { return err } handler, writer, err := NewStorageMapperHandler(mapping.TargetFile, mapping.TargetPackage, mapping.UseTextFormat, binaryFormats) if err != nil { return err } defer writer.Close() destinationURL := "mem://" + mapping.DestinationURI err = copyStorageContent(sourceService, mapping.SourceURL, destinationService, destinationURL, nil, "", handler) if err != nil { return err } } return nil } //NewStorageMapperHandler creates a template handler for generating go file that write static content into memory service. func NewStorageMapperHandler(filename, pkg string, useTextFormat bool, binaryFormat map[string]bool) (CopyHandler, io.WriteCloser, error) { _ = toolbox.RemoveFileIfExist(filename) writer, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, nil, err } template := &templateWriter{writer} _ = template.Init(pkg) return func(sourceObject Object, source io.Reader, destinationService Service, destinationURL string) error { _, name := toolbox.URLSplit(destinationURL) if strings.HasPrefix(name, ".") { //skip hidden files return nil } content, err := ioutil.ReadAll(source) if err != nil { return err } isText := useTextFormat ext := path.Ext(destinationURL) if binaryFormat[ext] { isText = false } template.WriteStorageContent(destinationURL, content, isText) return nil }, template, nil } type templateWriter struct { io.WriteCloser } func (t *templateWriter) Init(pkg string) error { var begin = `package %v import ( "bytes" "github.com/viant/toolbox/storage" "log" ) func init() { var memStorage = storage.NewMemoryService(); ` _, err := t.Write([]byte(fmt.Sprintf(begin, pkg))) return err } func (t *templateWriter) WriteStorageContent(URL string, content []byte, useText bool) error { if useText { text := string(content) var count = strings.Count(text, "`") if count > 0 { text = strings.Replace(text, "`", "`+\"`\"+`", count) content = []byte(text) } } var contentReader = fmt.Sprintf("bytes.NewReader([]byte(`%s`))", content) if !toolbox.IsASCIIText(contentReader) && !useText { var byteArray = make([]string, 0) for _, b := range content { byteArray = append(byteArray, fmt.Sprintf("%d", b)) } contentReader = fmt.Sprintf("bytes.NewReader([]byte{%v})", strings.Join(byteArray, ",")) } var payload = ` { err := memStorage.Upload("%v", %v) if err != nil { log.Printf("failed to upload: %v %v", err) } } ` payload = fmt.Sprintf(payload, URL, contentReader, URL, "%v") _, err := t.Write([]byte(payload)) return err } func (t *templateWriter) Close() error { var end = "}\n" _, err := t.Write([]byte(end)) t.WriteCloser.Close() return err } toolbox-0.33.2/storage/mapper_test.go000066400000000000000000000017611374110251100176030ustar00rootroot00000000000000package storage_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" "io/ioutil" "os" "path" "testing" ) func TestTemplateWriter_GenerateStorageCode(t *testing.T) { parent := toolbox.CallerDirectory(3) var source = toolbox.FileSchema + path.Join(parent, "test", "source") var destination = path.Join("test", "source") var target = path.Join(parent, "test", "gen", "source.go") parent, _ = path.Split(target) toolbox.CreateDirIfNotExist(parent) err := storage.GenerateStorageCode(&storage.StorageMapping{ SourceURL: source, DestinationURI: destination, TargetFile: target, TargetPackage: "gen", }) assert.Nil(t, err) reader, err := os.Open(target) assert.Nil(t, err) defer reader.Close() content, err := ioutil.ReadAll(reader) if assert.Nil(t, err) { textContent := string(content) assert.Contains(t, textContent, `err := memStorage.Upload("mem://test/source/file2.txt", bytes.NewReader([]byte`) } } toolbox-0.33.2/storage/mem_service.go000066400000000000000000000153501374110251100175550ustar00rootroot00000000000000package storage import ( "bytes" "errors" "io" "io/ioutil" "net/url" "os" "strings" "sync" "time" ) const MemoryProviderScheme = "mem" var noSuchFileOrDirectoryError = errors.New("No such file or directory") var folderMode, _ = NewFileMode("drwxrwxrwx") //MemoryRoot represents memory root storage var MemoryRoot = newMemoryFolder("mem:///", NewFileInfo("/", 102, folderMode, time.Now(), true)) //ResetMemory reset memory root storage func ResetMemory() { MemoryRoot = newMemoryFolder("mem:///", NewFileInfo("/", 102, folderMode, time.Now(), true)) } //Service represents memory storage service intended for testing type memoryStorageService struct { root *MemoryFolder } type MemoryFile struct { name string fileInfo os.FileInfo content []byte } func (f *MemoryFile) Object() Object { return NewAbstractStorageObject(f.name, f, f.fileInfo) } type MemoryFolder struct { name string fileInfo os.FileInfo mutext *sync.RWMutex files map[string]*MemoryFile folders map[string]*MemoryFolder } func (f *MemoryFolder) Object() Object { return NewAbstractStorageObject(f.name, f, f.fileInfo) } func (f *MemoryFolder) Objects() []Object { var result = make([]Object, 0) result = append(result, f.Object()) for _, folder := range f.folders { result = append(result, folder.Object()) } for _, file := range f.files { result = append(result, file.Object()) } return result } func newMemoryFolder(name string, info os.FileInfo) *MemoryFolder { return &MemoryFolder{ name: name, fileInfo: info, mutext: &sync.RWMutex{}, files: make(map[string]*MemoryFile), folders: make(map[string]*MemoryFolder), } } func (s *memoryStorageService) getFolder(pathFragments []string) (*MemoryFolder, error) { node := s.root var ok bool for i := 1; i+1 < len(pathFragments); i++ { pathFragment := pathFragments[i] node, ok = node.folders[pathFragment] if !ok { return nil, noSuchFileOrDirectoryError } } return node, nil } func (s *memoryStorageService) getPath(URL string) (string, error) { parsedURL, err := url.Parse(URL) if err != nil { return "", err } parsedPath := parsedURL.Path strings.Replace(parsedPath, "//", "/", len(parsedPath)) if len(parsedPath) > 1 && strings.HasSuffix(parsedPath, "/") { parsedPath = string(parsedPath[:len(parsedPath)-1]) } return parsedPath, nil } //List returns a list of object for supplied url func (s *memoryStorageService) List(URL string) ([]Object, error) { path, err := s.getPath(URL) if err != nil { return nil, err } if path == "/" { return s.root.Objects(), nil } var pathFragments = strings.Split(path, "/") node, err := s.getFolder(pathFragments) if err != nil { return nil, err } var pathLeaf = pathFragments[len(pathFragments)-1] if memoryFile, ok := node.files[pathLeaf]; ok { return []Object{memoryFile.Object()}, nil } if folder, ok := node.folders[pathLeaf]; ok { return folder.Objects(), nil } return []Object{}, nil } //Exists returns true if resource exists func (s *memoryStorageService) Exists(URL string) (bool, error) { objects, err := s.List(URL) if err != nil { return false, err } return len(objects) > 0, nil } func (s *memoryStorageService) Close() error { return nil } //Object returns a Object for supplied url func (s *memoryStorageService) StorageObject(URL string) (Object, error) { objects, err := s.List(URL) if err != nil { return nil, err } if len(objects) == 0 { return nil, noSuchFileOrDirectoryError } return objects[0], nil } //Download returns reader for downloaded storage object func (s *memoryStorageService) Download(object Object) (io.ReadCloser, error) { var urlPath, err = s.getPath(object.URL()) if err != nil { return nil, err } var pathFragments = strings.Split(urlPath, "/") node, err := s.getFolder(pathFragments) if err != nil { return nil, err } var pathLeaf = pathFragments[len(pathFragments)-1] if memoryFile, ok := node.files[pathLeaf]; ok { return ioutil.NopCloser(bytes.NewReader(memoryFile.content)), nil } return nil, noSuchFileOrDirectoryError } //Upload uploads provided reader content for supplied url. func (s *memoryStorageService) Upload(URL string, reader io.Reader) error { return s.UploadWithMode(URL, DefaultFileMode, reader) } //Upload uploads provided reader content for supplied url. func (s *memoryStorageService) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { urlPath, err := s.getPath(URL) if err != nil { return err } content, err := ioutil.ReadAll(reader) if err != nil { return err } var node = s.root var pathFragments = strings.Split(urlPath, "/") for i := 1; i+1 < len(pathFragments); i++ { pathFragment := pathFragments[i] if subFolder, ok := node.folders[pathFragment]; ok { node = subFolder } else { var folderURL = MemoryProviderScheme + "://" + strings.Join(pathFragments[:i+1], "/") var folderInfo = NewFileInfo(pathFragment, 102, folderMode, time.Now(), true) newFolder := newMemoryFolder(folderURL, folderInfo) node.mutext.Lock() node.folders[folderInfo.Name()] = newFolder node.mutext.Unlock() node = newFolder } } var pathLeaf = pathFragments[len(pathFragments)-1] fileInfo := NewFileInfo(pathLeaf, int64(len(content)), fileMode, time.Now(), false) var memoryFile = &MemoryFile{name: URL, content: content, fileInfo: fileInfo} node.files[fileInfo.Name()] = memoryFile return nil } func (s *memoryStorageService) Register(schema string, service Service) error { return errors.New("unsupported") } //Delete removes passed in storage object func (s *memoryStorageService) Delete(object Object) error { var urlPath, err = s.getPath(object.URL()) if err != nil { return err } var pathFragments = strings.Split(urlPath, "/") node, err := s.getFolder(pathFragments) if err != nil { return err } var pathLeaf = pathFragments[len(pathFragments)-1] if _, ok := node.files[pathLeaf]; ok { delete(node.files, pathLeaf) return nil } if _, ok := node.files[pathLeaf]; ok { delete(node.folders, pathLeaf) return nil } return noSuchFileOrDirectoryError } //DownloadWithURL downloads content for passed in object URL func (s *memoryStorageService) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } // creates a new private memory service func NewPrivateMemoryService() Service { return &memoryStorageService{ root: newMemoryFolder("mem:///", NewFileInfo("/", 102, folderMode, time.Now(), true)), } } //creates a new memory service func NewMemoryService() Service { return &memoryStorageService{ root: MemoryRoot, } } func init() { Registry().Registry[MemoryProviderScheme] = memServiceProvider } func memServiceProvider(credentialFile string) (Service, error) { return NewMemoryService(), nil } toolbox-0.33.2/storage/mem_service_test.go000066400000000000000000000060241374110251100206120ustar00rootroot00000000000000package storage_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" "io/ioutil" "strings" "testing" ) func Test_NewMemoryService(t *testing.T) { storage.ResetMemory() service := storage.NewMemoryService() var files = map[string]string{ "mem:///test/file1.txt": "abc", "mem:///test/file2.txt": "xyz", "mem:///test/sub/file1.txt": "---", "mem:///test/sub/file2.txt": "xxx", } for k, v := range files { err := service.Upload(k, strings.NewReader(v)) assert.Nil(t, err) } for k, v := range files { object, err := service.StorageObject(k) if assert.Nil(t, err) { reader, err := service.Download(object) if assert.Nil(t, err) { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, v, string(content)) } } } { objects, err := service.List("mem:///") assert.Nil(t, err) assert.Equal(t, 2, len(objects)) assert.True(t, objects[0].IsFolder()) assert.EqualValues(t, "mem:///", objects[0].URL()) assert.True(t, objects[1].IsFolder()) assert.EqualValues(t, "mem:///test", objects[1].URL()) } { objects, err := service.List("mem:///test") assert.Nil(t, err) assert.Equal(t, 4, len(objects)) assert.True(t, objects[0].IsFolder()) assert.EqualValues(t, "mem:///test", objects[0].URL()) assert.True(t, objects[1].IsFolder()) assert.EqualValues(t, "mem:///test/sub", objects[1].URL()) } { objects, err := service.List("mem:///test/sub/") assert.Nil(t, err) assert.Equal(t, 3, len(objects)) assert.True(t, objects[0].IsFolder()) for k := range files { object, err := service.StorageObject(k) if assert.Nil(t, err) { err = service.Delete(object) assert.Nil(t, err) } } } } func TestMemCopy(t *testing.T) { service := storage.NewMemoryService() defer service.Close() var files = map[string]string{ "mem:///test/file1.txt": "abc", "mem:///test/file2.txt": "xyz", "mem:///test/sub/file1.txt": "---", "mem:///test/sub/file2.txt": "xxx", } for k, v := range files { err := service.Upload(k, strings.NewReader(v)) assert.Nil(t, err) } for k, v := range files { object, err := service.StorageObject(k) if assert.Nil(t, err) { reader, err := service.Download(object) if assert.Nil(t, err) { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, v, string(content)) } } } baseUrl := "mem://" sourceURL := toolbox.URLPathJoin(baseUrl, "/test/") targetURL := toolbox.URLPathJoin(baseUrl, "/target") err := storage.Copy(service, sourceURL, service, targetURL, nil, nil) assert.Nil(t, err) for k, v := range files { k = strings.Replace(k, "mem:///test/", "mem:///target/", 1) object, err := service.StorageObject(k) if assert.Nil(t, err) { reader, err := service.Download(object) if assert.Nil(t, err, k) { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, v, string(content)) } } } } toolbox-0.33.2/storage/object.go000066400000000000000000000035571374110251100165330ustar00rootroot00000000000000package storage import ( "os" ) const ( undefined int = iota StorageObjectFolderType //folder type StorageObjectContentType //file type ) //Object represents a storage object type Object interface { //URL return storage url URL() string //Type returns storage type either folder or file Type() int //IsFolder returns true if object is a folder IsFolder() bool //IsContent returns true if object is a file IsContent() bool //Wrap wraps source storage object Wrap(source interface{}) //Unwrap unwraps source storage object into provided target. Unwrap(target interface{}) error FileInfo() os.FileInfo } //AbstractObject represents abstract storage object type AbstractObject struct { Object url string objectType int Source interface{} fileInfo os.FileInfo } //URL return storage url func (o *AbstractObject) URL() string { return o.url } //Type returns storage type StorageObjectFolderType or StorageObjectContentType func (o *AbstractObject) Type() int { return o.objectType } //IsFolder returns true if object is a folder func (o *AbstractObject) IsFolder() bool { return o.objectType == StorageObjectFolderType } //IsContent returns true if object is a file func (o *AbstractObject) IsContent() bool { return o.objectType == StorageObjectContentType } //Wrap wraps source storage object func (o *AbstractObject) Wrap(source interface{}) { o.Source = source } func (o *AbstractObject) FileInfo() os.FileInfo { return o.fileInfo } //NewAbstractStorageObject creates a new abstract storage object func NewAbstractStorageObject(url string, source interface{}, fileInfo os.FileInfo) *AbstractObject { var result = &AbstractObject{ url: url, Source: source, fileInfo: fileInfo, objectType: StorageObjectContentType, } if fileInfo.IsDir() { result.objectType = StorageObjectFolderType } return result } toolbox-0.33.2/storage/provider.go000066400000000000000000000001651374110251100171070ustar00rootroot00000000000000package storage //Provider represetns a service provider type Provider func(credentialFile string) (Service, error) toolbox-0.33.2/storage/registry.go000066400000000000000000000007021374110251100171220ustar00rootroot00000000000000package storage type registry struct { Registry map[string]Provider } func (p *registry) Get(namespace string) func(credentialFile string) (Service, error) { return p.Registry[namespace] } var registrySingleton *registry //Registry returns new provider func Registry() *registry { if registrySingleton != nil { return registrySingleton } registrySingleton = ®istry{ Registry: make(map[string]Provider), } return registrySingleton } toolbox-0.33.2/storage/s3/000077500000000000000000000000001374110251100152515ustar00rootroot00000000000000toolbox-0.33.2/storage/s3/object.go000066400000000000000000000017721374110251100170550ustar00rootroot00000000000000package s3 import ( "fmt" "github.com/aws/aws-sdk-go/service/s3" "github.com/viant/toolbox/storage" "os" ) type object struct { *storage.AbstractObject } func (o *object) Unwrap(target interface{}) error { if commonPrefix, casted := target.(**s3.CommonPrefix); casted { source, ok := o.Source.(*s3.CommonPrefix) if !ok { return fmt.Errorf("failed to case %T into %T", o.Source, target) } *commonPrefix = source } if commonPrefix, casted := target.(**s3.Object); casted { source, ok := o.Source.(*s3.Object) if !ok { return fmt.Errorf("failed to case %T into %T", o.Source, target) } *commonPrefix = source } return fmt.Errorf("unsuported target %T", target) } //newStorageObject creates a new aws storage object func newStorageObject(url string, source interface{}, fileInfo os.FileInfo) storage.Object { abstract := storage.NewAbstractStorageObject(url, source, fileInfo) result := &object{ AbstractObject: abstract, } result.AbstractObject.Object = result return result } toolbox-0.33.2/storage/s3/provider.go000066400000000000000000000020231374110251100174270ustar00rootroot00000000000000package s3 import ( "github.com/viant/toolbox/cred" "os" "path" "path/filepath" "strings" "github.com/viant/toolbox/storage" "github.com/viant/toolbox/url" ) const ProviderScheme = "s3" func init() { SetDefaultProvider() } func serviceProvider(credentialFile string) (storage.Service, error) { s3config := &cred.Config{} if credentialFile != "" { if !strings.HasPrefix(credentialFile, "/") { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) credentialFile = path.Join(dir, credentialFile) } resource := url.NewResource(credentialFile) err := resource.Decode(s3config) if err != nil { return nil, err } } return NewService(s3config), nil } //SetProvider set s3 provider with dynamic credentials func SetDefaultProvider() { storage.Registry().Registry[ProviderScheme] = serviceProvider } //SetProvider set s3 provider with supplied config func SetProvider(config *cred.Config) { storage.Registry().Registry[ProviderScheme] = func(string) (storage.Service, error) { return NewService(config), nil } } toolbox-0.33.2/storage/s3/provider_test.go000066400000000000000000000011371374110251100204730ustar00rootroot00000000000000package s3 import ( "path" "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" ) const s3Url = "s3://path" const s3Secret = "mock_s3.json" //Troubleshooting prod issue with credentials not loading func TestLoadingCredentialFile(t *testing.T) { // Creating absolute path fileName, _, _ := toolbox.CallerInfo(2) currentPath, _ := path.Split(fileName) credentialPath := path.Dir(path.Dir(currentPath)) + "/test/" + s3Secret service, err := storage.NewServiceForURL(s3Url, credentialPath) assert.Nil(t, err) assert.NotNil(t, service) } toolbox-0.33.2/storage/s3/service.go000066400000000000000000000155151374110251100172470ustar00rootroot00000000000000package s3 import ( "bytes" "fmt" "github.com/viant/toolbox/cred" "io" "net/url" "strings" "io/ioutil" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" "os" ) var defaultTime = time.Time{} type service struct { config *cred.Config } func listFolders(client *s3.S3, url *url.URL, result *[]storage.Object) error { folderRequest := &s3.ListObjectsInput{ Bucket: aws.String(url.Host), Prefix: aws.String(url.Path[1:]), Delimiter: aws.String("/"), } prefixes := make([]*s3.CommonPrefix, 0) err := client.ListObjectsPages(folderRequest, func(page *s3.ListObjectsOutput, lastPage bool) bool { prefixes = append(prefixes, page.CommonPrefixes...) return len(page.CommonPrefixes) > 0 }) if err != nil { if strings.Contains(err.Error(), "BucketRegionError") { return nil } return err } for _, prefix := range prefixes { pathURL := "s3://" + url.Host + "/" + *prefix.Prefix var _, name = toolbox.URLSplit(pathURL) var fileMode, _ = storage.NewFileMode("drw-rw-rw-") var fileInfo = storage.NewFileInfo(name, 102, fileMode, defaultTime, fileMode.IsDir()) var object = newStorageObject(pathURL, prefix, fileInfo) *result = append(*result, object) } return nil } func listContent(client *s3.S3, parsedURL *url.URL, result *[]storage.Object) error { var path = parsedURL.Path folderRequest := &s3.ListObjectsInput{ Bucket: aws.String(parsedURL.Host), Delimiter: aws.String("/"), } if len(path) > 0 { folderRequest.Prefix = aws.String(parsedURL.Path[1:]) } contents := make([]*s3.Object, 0) err := client.ListObjectsPages(folderRequest, func(page *s3.ListObjectsOutput, lastPage bool) bool { contents = append(contents, page.Contents...) return len(page.Contents) > 0 }) if err != nil { if strings.Contains(err.Error(), "BucketRegionError") { return nil } return err } for _, content := range contents { objectURL := "s3://" + parsedURL.Host + "/" + *content.Key var _, name = toolbox.URLSplit(objectURL) var fileMode, _ = storage.NewFileMode("-rw-rw-rw-") var fileInfo = storage.NewFileInfo(name, *content.Size, fileMode, *content.LastModified, fileMode.IsDir()) var object = newStorageObject(objectURL, content, fileInfo) *result = append(*result, object) } return nil } func (s *service) getAwsConfig() (*aws.Config, error) { if s.config.Secret == "" { return aws.NewConfig().WithRegion(s.config.Region), nil } awsCredentials := credentials.NewStaticCredentials(s.config.Key, s.config.Secret, s.config.Token) _, err := awsCredentials.Get() if err != nil { return nil, fmt.Errorf("bad credentials: %s", err) } return aws.NewConfig().WithRegion(s.config.Region).WithCredentials(awsCredentials), nil } func (s *service) List(URL string) ([]storage.Object, error) { var result = make([]storage.Object, 0) u, err := url.Parse(URL) if err != nil { return nil, fmt.Errorf("failed to parse : %v", err) } config, err := s.getAwsConfig() if err != nil { return nil, fmt.Errorf("failed to get aws config: %v", err) } client := s3.New(session.New(), config) err = listFolders(client, u, &result) if err == nil { err = listContent(client, u, &result) } if err != nil { return nil, fmt.Errorf("failed to get list content: %v", err) } return result, nil } func (s *service) Exists(URL string) (bool, error) { objects, err := s.List(URL) if err != nil { return false, err } return len(objects) > 0, nil } func (s *service) StorageObject(URL string) (storage.Object, error) { objects, err := s.List(URL) if err != nil { return nil, err } if len(objects) == 0 { return nil, fmt.Errorf("Not found %v", URL) } return objects[0], nil } func (s *service) Download(object storage.Object) (io.ReadCloser, error) { u, err := url.Parse(object.URL()) if err != nil { return nil, fmt.Errorf("failed to parse : %v", err) } config, err := s.getAwsConfig() if err != nil { return nil, fmt.Errorf("failed to get aws config: %v", err) } downloader := s3manager.NewDownloader(session.New(config)) target := &s3.Object{} _ = object.Unwrap(&target) writer := toolbox.NewByteWriterAt() _, err = downloader.Download(writer, &s3.GetObjectInput{ Bucket: aws.String(u.Host), Key: aws.String(*target.Key), }) if err != nil { return nil, fmt.Errorf("failed to download: %v", err) } return ioutil.NopCloser(bytes.NewReader(writer.Buffer)), nil } func (s *service) Upload(URL string, reader io.Reader) error { return s.UploadWithMode(URL, storage.DefaultFileMode, reader) } func (s *service) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { err := s.uploadContent(URL, reader) if toolbox.IsNotFoundError(err) { config, err := s.getAwsConfig() if err != nil { return err } if parserURL, err := url.Parse(URL); err == nil { client := s3.New(session.New(config)) if _, err := client.CreateBucket(&s3.CreateBucketInput{ Bucket: &parserURL.Host, }); err != nil { return err } } return s.uploadContent(URL, reader) } return err } func (s *service) uploadContent(URL string, reader io.Reader) error { parsedURL, err := url.Parse(URL) if err != nil { return err } config, err := s.getAwsConfig() if err != nil { return err } uploader := s3manager.NewUploader(session.New(config)) _, err = uploader.Upload(&s3manager.UploadInput{ Body: reader, Bucket: aws.String(parsedURL.Host), Key: aws.String(parsedURL.Path), }) if err != nil { return toolbox.ReclassifyNotFoundIfMatched(err, URL) } return nil } func (s *service) Delete(object storage.Object) error { parsedURL, err := url.Parse(object.URL()) if err != nil { return err } if object.IsFolder() { var objects = []storage.Object{} objects, err = s.List(object.URL()) if err != nil { return err } for _, object := range objects { if err := s.Delete(object); err != nil { return err } } return nil } target := &s3.Object{} object.Unwrap(&target) request := &s3.DeleteObjectInput{ Bucket: aws.String(parsedURL.Host), Key: target.Key, } config, err := s.getAwsConfig() if err != nil { return err } client := s3.New(session.New(), config) client.DeleteObject(request) return nil } func (s *service) Register(schema string, service storage.Service) error { return fmt.Errorf("Unsupported") } //DownloadWithURL downloads content for passed in object URL func (s *service) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } func (s *service) Close() error { return nil } //NewService creates a new aws storage service func NewService(config *cred.Config) storage.Service { return &service{config: config} } toolbox-0.33.2/storage/s3/service_test.go000066400000000000000000000010141374110251100202730ustar00rootroot00000000000000package s3_test import ( "testing" ) func TestService_List(t *testing.T) { /* fmt.Print("Has service\n") config := &aws.Config{ Region: "", Key: "", Secret: "", } service := aws.NewService(config) assert.NotNil(t, service) result, err := service.List("") assert.Nil(t, err) for _, o := range result { fmt.Printf("R: %v\n", o.URL()) } obj, err := service.StorageObject("") assert.Nil(t, err) assert.NotNil(t, obj) assert.Nil(t, service.Delete(obj))*/ } toolbox-0.33.2/storage/scp/000077500000000000000000000000001374110251100155115ustar00rootroot00000000000000toolbox-0.33.2/storage/scp/fileinfo_parser.go000066400000000000000000000135451374110251100212170ustar00rootroot00000000000000package scp import ( "fmt" "github.com/lunixbochs/vtclean" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" "net/url" "strings" "time" "unicode" ) const ( fileInfoPermission = iota _ fileInfoOwner fileInfoGroup fileInfoSize fileInfoDateMonth fileInfoDateDay fileInfoDateHour fileInfoDateYear fileInfoName ) const ( fileIsoInfoPermission = iota _ fileIsoInfoOwner fileIsoInfoGroup fileIsoInfoSize fileIsoDate fileIsoTime fileIsoTimezone fileIsoInfoName ) //Parser represents fileinfo parser from stdout type Parser struct { IsoTimeStyle bool } func (p *Parser) Parse(parsedURL *url.URL, stdout string, isURLDir bool) ([]storage.Object, error) { var err error var result = make([]storage.Object, 0) if strings.Contains(stdout, "No such file or directory") { return result, nil } for _, line := range strings.Split(stdout, "\n") { if line == "" { continue } var object storage.Object if p.IsoTimeStyle { if object, err = p.extractObjectFromIsoBasedTimeCommand(parsedURL, line, isURLDir); err != nil { object, err = p.extractObjectFromNonIsoBaseTimeCommand(parsedURL, line, isURLDir) } } else { if object, err = p.extractObjectFromNonIsoBaseTimeCommand(parsedURL, line, isURLDir); err != nil { object, err = p.extractObjectFromIsoBasedTimeCommand(parsedURL, line, isURLDir) } } if err != nil { return nil, err } result = append(result, object) } return result, nil } func (p *Parser) HasNextTokenInout(nextTokenPosition int, line string) bool { if nextTokenPosition >= len(line) { return false } nextToken := []rune(string(line[nextTokenPosition:]))[0] return !unicode.IsSpace(nextToken) } func (p *Parser) newObject(parsedURL *url.URL, name, permission, line, size string, modificationTime time.Time, isURLDirectory bool) (storage.Object, error) { var URLPath = parsedURL.Path var URL = parsedURL.String() var pathPosition = strings.Index(URL, parsedURL.Host) + len(parsedURL.Host) var URLPrefix = URL[:pathPosition] fileMode, err := storage.NewFileMode(permission) if err != nil { return nil, fmt.Errorf("failed to parse line for lineinfo: %v, unable to file attributes: %v", line, err) } if isURLDirectory { name = strings.Replace(name, URLPath, "", 1) URLPath = toolbox.URLPathJoin(URLPath, name) } else { URLPath = name } var objectURL = URLPrefix + URLPath fileInfo := storage.NewFileInfo(name, int64(toolbox.AsInt(size)), fileMode, modificationTime, fileMode.IsDir()) object := newStorageObject(objectURL, fileInfo, fileInfo) return object, nil } //extractObjectFromNonIsoBaseTimeCommand extract file storage object from line, // it expects a file info without iso i.e -rw-r--r-- 1 awitas 1742120565 414 Jun 8 14:14:08 2017 id_rsa.pub func (p *Parser) extractObjectFromNonIsoBaseTimeCommand(parsedURL *url.URL, line string, isURLDirectory bool) (storage.Object, error) { tokenIndex := 0 if strings.TrimSpace(line) == "" { return nil, nil } var owner, name, permission, group, size, year, month, day, hour string for i, aRune := range line { if unicode.IsSpace(aRune) { if p.HasNextTokenInout(i+1, line) { tokenIndex++ } continue } aChar := string(aRune) switch tokenIndex { case fileInfoPermission: permission += aChar case fileInfoOwner: owner += aChar case fileInfoGroup: group += aChar case fileInfoSize: if size == "" && !unicode.IsNumber(aRune) { tokenIndex-- group += " " + aChar continue } size += aChar case fileInfoDateMonth: month += aChar case fileInfoDateDay: day += aChar case fileInfoDateHour: hour += aChar case fileInfoDateYear: year += aChar case fileInfoName: name += aChar } } if name == "" { return nil, fmt.Errorf("failed to parse line for fileinfo: %v\n", line) } dateTime := year + " " + month + " " + day + " " + hour layout := toolbox.DateFormatToLayout("yyyy MMM ddd HH:mm:s") modificationTime, err := time.Parse(layout, dateTime) if err != nil { return nil, fmt.Errorf("failed to extract file info from stdout: %v, err: %v", line, err) } return p.newObject(parsedURL, name, permission, line, size, modificationTime, isURLDirectory) } //extractObjectFromNonIsoBaseTimeCommand extract file storage object from line, // it expects a file info with iso i.e. -rw-r--r-- 1 awitas awitas 2002 2017-11-04 22:29:33.363458941 +0000 aerospikeciads_aerospike.conf func (p *Parser) extractObjectFromIsoBasedTimeCommand(parsedURL *url.URL, line string, isURLDirectory bool) (storage.Object, error) { tokenIndex := 0 if strings.TrimSpace(line) == "" { return nil, nil } var owner, name, permission, group, timezone, date, modTime, size string line = vtclean.Clean(line, false) for i, aRune := range line { if unicode.IsSpace(aRune) { if p.HasNextTokenInout(i+1, line) { tokenIndex++ } continue } aChar := string(aRune) switch tokenIndex { case fileIsoInfoPermission: permission += aChar case fileIsoInfoOwner: owner += aChar case fileIsoInfoGroup: group += aChar case fileIsoInfoSize: if size == "" && !unicode.IsNumber(aRune) { tokenIndex-- group += " " + aChar continue } size += aChar case fileIsoDate: date += aChar case fileIsoTime: modTime += aChar case fileIsoTimezone: timezone += aChar case fileIsoInfoName: name += aChar } continue } timeLen := len(modTime) if timeLen > 12 { modTime = string(modTime[:12]) } dateTime := date + " " + modTime + " " + timezone layout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss.SSS ZZ") if len(date+" "+modTime) <= len("yyyy-MM-dd HH:mm:ss") { layout = toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss ZZ") } modificationTime, err := time.Parse(layout, dateTime) if err != nil { return nil, fmt.Errorf("failed to extract file info from stdout: %v, err: %v", line, err) } return p.newObject(parsedURL, name, permission, line, size, modificationTime, isURLDirectory) } toolbox-0.33.2/storage/scp/fileinfo_parser_test.go000066400000000000000000000051041374110251100222460ustar00rootroot00000000000000package scp_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox/storage/scp" "net/url" "testing" ) // -rw-r--r-- 1 awitas wheel 6668 Oct 25 11:41:44 2017 /build/dcm/dcm-server/target/classes/dcm.properties func Test_ExtractFileInfo(t *testing.T) { var parserURL, _ = url.Parse("scp://127.0.0.1/") { var parser = scp.Parser{IsoTimeStyle: false} objects, err := parser.Parse(parserURL, "-rw-r--r-- 1 awitas 1742120565 414 Jun 8 14:14:08 2017 f.pub", true) if assert.Nil(t, err) { var object = objects[0] assert.Equal(t, "scp://127.0.0.1/f.pub", object.URL()) assert.Equal(t, int64(1496931248), object.FileInfo().ModTime().Unix()) assert.Equal(t, int64(414), object.FileInfo().Size()) assert.Equal(t, true, object.IsContent()) } } { parserURL, _ = url.Parse("scp://127.0.0.1:22/") var parser = scp.Parser{IsoTimeStyle: true} var objects, err = parser.Parse(parserURL, "-rw-r--r-- 1 awitas awitas 2002 2017-11-04 22:29:33.363458941 +0000 aerospikeciads_aerospike.conf", true) if assert.Nil(t, err) { var object = objects[0] assert.Equal(t, "scp://127.0.0.1:22/aerospikeciads_aerospike.conf", object.URL()) assert.Equal(t, int64(1509834573), object.FileInfo().ModTime().Unix()) assert.Equal(t, int64(2002), object.FileInfo().Size()) assert.Equal(t, true, object.IsContent()) } } { parserURL, _ = url.Parse("scp://127.0.0.1:22/") var parser = scp.Parser{IsoTimeStyle: true} var objects, err = parser.Parse(parserURL, `-rw------- 1 myusername MYGROUP\\Domain Users 1679 Mar 8 15:27:22 2019 /Users/myusername/.ssh/git_id_rsa`, true) if assert.Nil(t, err) { var object = objects[0] assert.Equal(t, "scp://127.0.0.1:22/Users/myusername/.ssh/git_id_rsa", object.URL()) assert.Equal(t, int64(1552058842), object.FileInfo().ModTime().Unix()) assert.Equal(t, int64(1679), object.FileInfo().Size()) assert.Equal(t, true, object.IsContent()) } } { parserURL, _ = url.Parse("scp://127.0.0.1:22/") var parser = scp.Parser{IsoTimeStyle: true} var objects, err = parser.Parse(parserURL, `rwxr-xr-x@ 1 "github.com/viant/toolbox/storage/scp" WORKGROUP\\Domain Users 108143621 Apr 19 15:55:57 2019 /Users/ojoseph/git/test-app/../go-cm/dist/linux/go-cm`, true) if assert.Nil(t, err) { var object = objects[0] assert.Equal(t, "scp://127.0.0.1:22/Users/ojoseph/git/test-app/../go-cm/dist/linux/go-cm", object.URL()) assert.Equal(t, int64(1555689357), object.FileInfo().ModTime().Unix()) assert.Equal(t, int64(108143621), object.FileInfo().Size()) assert.Equal(t, true, object.IsContent()) } } } toolbox-0.33.2/storage/scp/object.go000066400000000000000000000013701374110251100173070ustar00rootroot00000000000000package scp import ( "github.com/viant/toolbox/storage" "os" ) type object struct { *storage.AbstractObject source interface{} permission string } //Wrap wraps source storage object func (i *object) Wrap(source interface{}) { i.source = source } //Unwrap unwraps source storage object into provided target. func (i *object) Unwrap(target interface{}) error { if result, ok := target.(**object); ok { *result = i } return nil } //newObject creates a new gc storage object func newStorageObject(URL string, source interface{}, fileInfo os.FileInfo) storage.Object { abstract := storage.NewAbstractStorageObject(URL, source, fileInfo) result := &object{ AbstractObject: abstract, } result.AbstractObject.Object = result return result } toolbox-0.33.2/storage/scp/provider.go000066400000000000000000000014331374110251100176730ustar00rootroot00000000000000package scp import ( "github.com/viant/toolbox/cred" "github.com/viant/toolbox/secret" "github.com/viant/toolbox/storage" "strings" ) //ProviderScheme represents scp URL scheme for this provider const ProviderScheme = "scp" //SSHProviderScheme represents ssh URL scheme for this provider const SSHProviderScheme = "ssh" func init() { storage.Registry().Registry[ProviderScheme] = serviceProvider storage.Registry().Registry[SSHProviderScheme] = serviceProvider } func serviceProvider(credentials string) (storage.Service, error) { var config = &cred.Config{} if strings.TrimSpace(credentials) != "" { var err error secrets := secret.New("", false) config, err = secrets.GetCredentials(credentials) if err != nil { return nil, err } } return NewService(config), nil } toolbox-0.33.2/storage/scp/service.go000066400000000000000000000205371374110251100175070ustar00rootroot00000000000000package scp import ( "bytes" "errors" "fmt" "github.com/lunixbochs/vtclean" "github.com/viant/toolbox" "github.com/viant/toolbox/cred" "github.com/viant/toolbox/ssh" "github.com/viant/toolbox/storage" "io" "io/ioutil" "net/url" "os" "path" "strings" "sync" ) const ( defaultSSHPort = 22 verificationSizeThreshold = 1024 * 1024 ) //NoSuchFileOrDirectoryError represents no such file or directory error var NoSuchFileOrDirectoryError = errors.New("No such file or directory") const unrecognizedOption = "unrecognized option" type service struct { fileService storage.Service config *cred.Config services map[string]ssh.Service multiSessions map[string]ssh.MultiCommandSession mutex *sync.Mutex } func (s *service) runCommand(session ssh.MultiCommandSession, URL string, command string) (string, error) { s.mutex.Lock() defer s.mutex.Unlock() output, _ := session.Run(command, nil, 5000) var stdout = s.stdout(output) return stdout, nil } func (s *service) stdout(output string) string { var result = make([]string, 0) lines := strings.Split(output, "\n") for _, line := range lines { result = append(result, vtclean.Clean(line, false)) } return strings.Join(result, "\n") } func (s *service) getMultiSession(parsedURL *url.URL) ssh.MultiCommandSession { s.mutex.Lock() defer s.mutex.Unlock() return s.multiSessions[parsedURL.Host] } func (s *service) getService(parsedURL *url.URL) (ssh.Service, error) { port := toolbox.AsInt(parsedURL.Port()) if port == 0 { port = 22 } key := parsedURL.Host s.mutex.Lock() defer s.mutex.Unlock() if service, ok := s.services[key]; ok { return service, nil } service, err := ssh.NewService(parsedURL.Hostname(), toolbox.AsInt(port), s.config) if err != nil { return nil, err } s.services[key] = service s.multiSessions[key], err = service.OpenMultiCommandSession(nil) if err != nil { return nil, err } return service, nil } //List returns a list of object for supplied URL func (s *service) List(URL string) ([]storage.Object, error) { parsedURL, err := url.Parse(URL) if err != nil { return nil, err } if parsedURL.Host == "127.0.0.1" || parsedURL.Host == "127.0.0.1:22" { var fileURL = toolbox.FileSchema + parsedURL.Path return s.fileService.List(fileURL) } _, err = s.getService(parsedURL) if err != nil { return nil, err } commandSession := s.getMultiSession(parsedURL) canListWithTimeStyle := commandSession.System() != "darwin" var parser = &Parser{IsoTimeStyle: canListWithTimeStyle} var URLPath = parsedURL.Path var result = make([]storage.Object, 0) var lsCommand = "" if canListWithTimeStyle { lsCommand += "ls -dltr --time-style=full-iso " + URLPath } else { lsCommand += "ls -dltrT " + URLPath } output, _ := s.runCommand(commandSession, URL, lsCommand) var stdout = vtclean.Clean(string(output), false) if strings.Contains(stdout, "unrecognized option") { if canListWithTimeStyle { lsCommand = "ls -dltr --full-time " + URLPath output, _ = s.runCommand(commandSession, URL, lsCommand) stdout = vtclean.Clean(string(output), false) } } if strings.Contains(stdout, unrecognizedOption) { return nil, fmt.Errorf("unable to list files with: %v, %v", lsCommand, stdout) } if strings.Contains(stdout, "No such file or directory") { return result, NoSuchFileOrDirectoryError } objects, err := parser.Parse(parsedURL, stdout, false) if err != nil { return nil, err } if len(objects) == 1 && objects[0].FileInfo().IsDir() { output, _ = s.runCommand(commandSession, URL, lsCommand+" "+path.Join(URLPath, "*")) stdout = vtclean.Clean(string(output), false) directoryObjects, err := parser.Parse(parsedURL, stdout, true) if err != nil { return nil, err } if len(directoryObjects) > 0 { objects = append(objects, directoryObjects...) } } return objects, nil } func (s *service) Exists(URL string) (bool, error) { parsedURL, err := url.Parse(URL) if err != nil { return false, err } if parsedURL.Host == "127.0.0.1" || parsedURL.Host == "127.0.0.1:22" { var fileURL = toolbox.FileSchema + parsedURL.Path return s.fileService.Exists(fileURL) } _, err = s.getService(parsedURL) if err != nil { return false, err } commandSession := s.getMultiSession(parsedURL) output, _ := s.runCommand(commandSession, URL, "ls -dltr "+parsedURL.Path) if strings.Contains(string(output), "No such file or directory") { return false, nil } return true, nil } func (s *service) StorageObject(URL string) (storage.Object, error) { objects, err := s.List(URL) if err != nil { return nil, err } if len(objects) == 0 { return nil, NoSuchFileOrDirectoryError } return objects[0], nil } //Download returns reader for downloaded storage object func (s *service) Download(object storage.Object) (io.ReadCloser, error) { if object == nil { return nil, fmt.Errorf("object was nil") } parsedURL, err := url.Parse(object.URL()) if err != nil { return nil, err } if parsedURL.Host == "127.0.0.1" || parsedURL.Host == "127.0.0.1:22" { var fileURL = toolbox.FileSchema + parsedURL.Path storageObject, err := s.fileService.StorageObject(fileURL) if err != nil { return nil, err } return s.fileService.Download(storageObject) } port := toolbox.AsInt(parsedURL.Port()) if port == 0 { port = defaultSSHPort } service, err := s.getService(parsedURL) if err != nil { return nil, err } content, err := service.Download(parsedURL.Path) if err != nil { return nil, err } return ioutil.NopCloser(bytes.NewReader(content)), nil } //Upload uploads provided reader content for supplied URL. func (s *service) Upload(URL string, reader io.Reader) error { return s.UploadWithMode(URL, storage.DefaultFileMode, reader) } //Upload uploads provided reader content for supplied URL. func (s *service) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { if mode == 0 { mode = storage.DefaultFileMode } parsedURL, err := url.Parse(URL) if err != nil { return err } if parsedURL.Host == "127.0.0.1" || parsedURL.Host == "127.0.0.1:22" { var fileURL = toolbox.FileSchema + parsedURL.Path return s.fileService.UploadWithMode(fileURL, mode, reader) } port := toolbox.AsInt(parsedURL.Port()) if port == 0 { port = defaultSSHPort } //service, err := ssh.NewService(parsedURL.Hostname(), toolbox.AsInt(port), s.config) service, err := s.getService(parsedURL) if err != nil { return err } //defer service.Close() content, err := ioutil.ReadAll(reader) if err != nil { return fmt.Errorf("failed to upload - unable read: %v", err) } err = service.Upload(parsedURL.Path, mode, content) if err != nil { return fmt.Errorf("failed to upload: %v %v", URL, err) } return err } func (s *service) Register(schema string, service storage.Service) error { return errors.New("unsupported") } func (s *service) Close() error { for _, service := range s.services { service.Close() } for _, session := range s.multiSessions { session.Close() } return nil } //Delete removes passed in storage object func (s *service) Delete(object storage.Object) error { parsedURL, err := url.Parse(object.URL()) if err != nil { return err } if parsedURL.Host == "127.0.0.1" || parsedURL.Host == "127.0.0.1:22" { var fileURL = toolbox.FileSchema + parsedURL.Path storageObject, err := s.fileService.StorageObject(fileURL) if err != nil { return err } return s.fileService.Delete(storageObject) } port := toolbox.AsInt(parsedURL.Port()) if port == 0 { port = defaultSSHPort } service, err := ssh.NewService(parsedURL.Hostname(), toolbox.AsInt(port), s.config) if err != nil { return err } //defer service.Close() session, err := service.NewSession() if err != nil { return err } defer session.Close() if parsedURL.Path == "/" { return fmt.Errorf("invalid removal path: %v", parsedURL.Path) } _, err = session.Output("rm -rf " + parsedURL.Path) return err } //DownloadWithURL downloads content for passed in object URL func (s *service) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } //NewService create a new gc storage service func NewService(config *cred.Config) *service { return &service{ services: make(map[string]ssh.Service), config: config, multiSessions: make(map[string]ssh.MultiCommandSession), mutex: &sync.Mutex{}, fileService: storage.NewFileStorage(), } } toolbox-0.33.2/storage/scp/service_test.go000066400000000000000000000023351374110251100205420ustar00rootroot00000000000000package scp_test import ( "github.com/stretchr/testify/assert" "testing" "github.com/viant/toolbox/storage/scp" "io/ioutil" "strings" ) func TestService_List(t *testing.T) { //deprecated use viant/afs istead //service := scp.NewService(nil) //assert.NotNil(t, service) //dir, home := path.Split(os.Getenv("HOME")) //objects, err := service.List("scp://127.0.0.1/" + dir) //if err == nil { // return //} //assert.Nil(t, err) //for _, object := range objects { // if strings.HasSuffix(object.URL(), home) { // assert.True(t, object.IsFolder()) // } //} } func TestService_Delete(t *testing.T) { service := scp.NewService(nil) assert.NotNil(t, service) err := service.Upload("scp://127.0.0.1//tmp/file.txt", strings.NewReader("this is test")) assert.Nil(t, err) objects, err := service.List("scp://127.0.0.1/tmp/") assert.Nil(t, err) assert.True(t, len(objects) > 1) object, err := service.StorageObject("scp://127.0.0.1//tmp/file.txt") assert.Nil(t, err) reader, err := service.Download(object) if err == nil { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, "this is test", string(content)) err = service.Delete(object) assert.Nil(t, nil) } } toolbox-0.33.2/storage/service.go000066400000000000000000000131541374110251100167170ustar00rootroot00000000000000// Package storage define abstract storage operation // Deprecated - please use https://github.com/viant/afs API instead // This package is frozen and no new functionality will be added, and future removal takes place. package storage import ( "fmt" "io" "io/ioutil" "net/url" "os" "strings" ) var DefaultFileMode os.FileMode = 0755 //Service represents abstract way to accessing local or remote storage type Service interface { //List returns a list of object for supplied url List(URL string) ([]Object, error) //Exists returns true if resource exists Exists(URL string) (bool, error) //Object returns a Object for supplied url StorageObject(URL string) (Object, error) //Download returns reader for downloaded storage object Download(object Object) (io.ReadCloser, error) //DownloadWithURL returns reader for downloaded URL object DownloadWithURL(URL string) (io.ReadCloser, error) //Upload uploads provided reader content for supplied storage object. Upload(URL string, reader io.Reader) error //Upload uploads provided reader content for supplied storage object. UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error //Delete removes passed in storage object Delete(object Object) error //Register register schema with provided service Register(schema string, service Service) error //Closes storage service Close() error } type storageService struct { registry map[string]Service } func (s *storageService) getServiceForSchema(URL string) (Service, error) { parsedUrl, err := url.Parse(URL) if err != nil { return nil, err } if result, found := s.registry[parsedUrl.Scheme]; found { return result, nil } return nil, fmt.Errorf("failed to lookup url schema %v in %v", parsedUrl.Scheme, URL) } //List lists all object for passed in URL func (s *storageService) List(URL string) ([]Object, error) { service, err := s.getServiceForSchema(URL) if err != nil { return nil, err } return service.List(URL) } //Exists returns true if resource exists func (s *storageService) Exists(URL string) (bool, error) { service, err := s.getServiceForSchema(URL) if err != nil { return false, err } return service.Exists(URL) } //StorageObject returns storage object for provided URL func (s *storageService) StorageObject(URL string) (Object, error) { service, err := s.getServiceForSchema(URL) if err != nil { return nil, err } return service.StorageObject(URL) } //Download downloads content for passed in object func (s *storageService) Download(object Object) (io.ReadCloser, error) { service, err := s.getServiceForSchema(object.URL()) if err != nil { return nil, err } return service.Download(object) } //DownloadWithURL downloads content for passed in object URL func (s *storageService) DownloadWithURL(URL string) (io.ReadCloser, error) { object, err := s.StorageObject(URL) if err != nil { return nil, err } return s.Download(object) } //Uploads content for passed in URL func (s *storageService) Upload(URL string, reader io.Reader) error { service, err := s.getServiceForSchema(URL) if err != nil { return err } return service.UploadWithMode(URL, 0644, reader) } //Uploads content for passed in URL func (s *storageService) UploadWithMode(URL string, mode os.FileMode, reader io.Reader) error { service, err := s.getServiceForSchema(URL) if err != nil { return err } return service.UploadWithMode(URL, mode, reader) } //Delete remove storage object func (s *storageService) Delete(object Object) error { service, err := s.getServiceForSchema(object.URL()) if err != nil { return err } return service.Delete(object) } //Close closes resources func (s *storageService) Close() error { for _, service := range s.registry { err := service.Close() if err != nil { return err } } return nil } //Register register storage schema func (s *storageService) Register(schema string, service Service) error { s.registry[schema] = service return nil } //NewService creates a new storage service func NewService() Service { var result = &storageService{ registry: make(map[string]Service), } _ = result.Register("file", &fileStorageService{}) _ = result.Register("mem", NewMemoryService()) return result } //NewServiceForURL creates a new storage service for provided URL scheme and optional credential file func NewServiceForURL(URL, credentials string) (Service, error) { parsedURL, err := url.Parse(URL) if err != nil { return nil, err } service := NewService() provider := Registry().Get(parsedURL.Scheme) if provider != nil { if len(credentials) > 0 { credentials = strings.Replace(credentials, "${env.HOME}", os.Getenv("HOME"), 1) if strings.HasPrefix(credentials, "~") { credentials = strings.Replace(credentials, "~", os.Getenv("HOME"), 1) } } serviceForScheme, err := provider(credentials) if err != nil { return nil, fmt.Errorf("failed lookup service for %v: %v", parsedURL.Scheme, err) } err = service.Register(parsedURL.Scheme, serviceForScheme) if err != nil { return nil, err } } else if parsedURL.Scheme != "file" { return nil, fmt.Errorf("unsupported scheme %v", URL) } return service, nil } //Download returns a download reader for supplied URL func Download(service Service, URL string) (io.ReadCloser, error) { object, err := service.StorageObject(URL) if err != nil { return nil, err } return service.Download(object) } //DownloadText returns a text for supplied URL func DownloadText(service Service, URL string) (string, error) { reader, err := Download(service, URL) if err != nil { return "", err } defer reader.Close() content, err := ioutil.ReadAll(reader) if err != nil { return "", err } return string(content), nil } toolbox-0.33.2/storage/service_test.go000066400000000000000000000042731374110251100177600ustar00rootroot00000000000000package storage_test import ( "bytes" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" _ "github.com/viant/toolbox/storage/scp" "io/ioutil" "os" "os/exec" "path" "strings" "testing" ) func TestStorageService_List(t *testing.T) { service := storage.NewService() assert.NotNil(t, service) fileName, _, _ := toolbox.CallerInfo(2) parent, _ := path.Split(fileName) baseUrl := "file://" + parent + "/test" if toolbox.FileExists(parent + "/test/file3.txt") { os.Remove(parent + "/test/file3.txt") } defer os.Remove(parent + "/test/file3.txt") objects, err := service.List(baseUrl) assert.Nil(t, err) assert.True(t, len(objects) >= 5) var objectByUrl = make(map[string]storage.Object) for _, object := range objects { objectByUrl[object.URL()] = object } assert.NotNil(t, objectByUrl[baseUrl+"/dir"]) assert.NotNil(t, objectByUrl[baseUrl+"/file1.txt"]) assert.NotNil(t, objectByUrl[baseUrl+"/file2.txt"]) assert.True(t, objectByUrl[baseUrl+"/dir"].IsFolder()) assert.True(t, objectByUrl[baseUrl+"/file2.txt"].IsContent()) { reader, err := service.Download(objectByUrl[baseUrl+"/file2.txt"]) if assert.Nil(t, err) { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, "line1\nline2", string(content)) } } var newFileUrl = baseUrl + "/file3.txt" err = service.Upload(baseUrl+"/file3.txt", bytes.NewReader([]byte("abc"))) assert.Nil(t, err) exists, err := service.Exists(baseUrl + "/file3.txt") assert.Nil(t, err) assert.True(t, exists) { object, err := service.StorageObject(newFileUrl) assert.Nil(t, err) reader, err := service.Download(object) if assert.Nil(t, err) { defer reader.Close() content, err := ioutil.ReadAll(reader) assert.Nil(t, err) assert.Equal(t, "abc", string(content)) } } } func TestUpload(t *testing.T) { var path = "/tmp/local/test.txt" toolbox.RemoveFileIfExist(path) exec.Command("rmdir /tmp/local").CombinedOutput() var destination = "scp://127.0.0.1/" + path service, err := storage.NewServiceForURL(destination, "") assert.Nil(t, err) err = service.Upload(destination, strings.NewReader("abc")) assert.Nil(t, err) } toolbox-0.33.2/storage/test/000077500000000000000000000000001374110251100157035ustar00rootroot00000000000000toolbox-0.33.2/storage/test/dir/000077500000000000000000000000001374110251100164615ustar00rootroot00000000000000toolbox-0.33.2/storage/test/dir/file.json000066400000000000000000000000401374110251100202650ustar00rootroot00000000000000[{ "id":1, "name":"name1" }]toolbox-0.33.2/storage/test/file1.txt000066400000000000000000000007211374110251100174440ustar00rootroot00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ultricies nisl id quam laoreet porttitor quis eu nulla. Sed pellentesque sit amet turpis a accumsan. Maecenas id odio ac dolor egestas iaculis. Maecenas non velit gravida, egestas nunc eget, dictum nisi. Quisque lorem ex, ullamcorper ac justo ut, vehicula sollicitudin risus. Duis imperdiet facilisis gravida. In hac habitasse platea dictumst. Ut sollicitudin lectus tortor. Fusce eu turpis justo.toolbox-0.33.2/storage/test/file2.txt000066400000000000000000000000131374110251100174370ustar00rootroot00000000000000line1 line2toolbox-0.33.2/storage/test/mock_s3.json000066400000000000000000000001171374110251100201330ustar00rootroot00000000000000{ "Region":"us-east-1", "Key":"MOCKKEY", "Secret":"thisisasecretstring" }toolbox-0.33.2/storage/test/source/000077500000000000000000000000001374110251100172035ustar00rootroot00000000000000toolbox-0.33.2/storage/test/source/dir/000077500000000000000000000000001374110251100177615ustar00rootroot00000000000000toolbox-0.33.2/storage/test/source/dir/file.json000066400000000000000000000000401374110251100215650ustar00rootroot00000000000000[{ "id":1, "name":"name1" }]toolbox-0.33.2/storage/test/source/dir2/000077500000000000000000000000001374110251100200435ustar00rootroot00000000000000toolbox-0.33.2/storage/test/source/dir2/subdir/000077500000000000000000000000001374110251100213335ustar00rootroot00000000000000toolbox-0.33.2/storage/test/source/dir2/subdir/file1.txt000066400000000000000000000007211374110251100230740ustar00rootroot00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ultricies nisl id quam laoreet porttitor quis eu nulla. Sed pellentesque sit amet turpis a accumsan. Maecenas id odio ac dolor egestas iaculis. Maecenas non velit gravida, egestas nunc eget, dictum nisi. Quisque lorem ex, ullamcorper ac justo ut, vehicula sollicitudin risus. Duis imperdiet facilisis gravida. In hac habitasse platea dictumst. Ut sollicitudin lectus tortor. Fusce eu turpis justo.toolbox-0.33.2/storage/test/source/file1.txt000066400000000000000000000007211374110251100207440ustar00rootroot00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ultricies nisl id quam laoreet porttitor quis eu nulla. Sed pellentesque sit amet turpis a accumsan. Maecenas id odio ac dolor egestas iaculis. Maecenas non velit gravida, egestas nunc eget, dictum nisi. Quisque lorem ex, ullamcorper ac justo ut, vehicula sollicitudin risus. Duis imperdiet facilisis gravida. In hac habitasse platea dictumst. Ut sollicitudin lectus tortor. Fusce eu turpis justo.toolbox-0.33.2/storage/test/source/file2.txt000066400000000000000000000000131374110251100207370ustar00rootroot00000000000000line1 line2toolbox-0.33.2/struct_helper.go000066400000000000000000000371621374110251100165030ustar00rootroot00000000000000package toolbox import ( "fmt" "github.com/go-errors/errors" "reflect" "strings" ) const ( fieldNameKey = "fieldName" anonymousKey = "anonymous" fieldIndexKey = "fieldIndex" defaultKey = "default" ) var columnMapping = []string{"column", "dateLayout", "dateFormat", "autoincrement", "primaryKey", "sequence", "valueMap", defaultKey, anonymousKey} //ScanStructFunc scan supplied struct methods func ScanStructMethods(structOrItsType interface{}, depth int, handler func(method reflect.Method) error) error { var scanned = make(map[reflect.Type]bool) return scanStructMethods(structOrItsType, scanned, depth, handler) } func scanStructMethods(structOrItsType interface{}, scanned map[reflect.Type]bool, depth int, handler func(method reflect.Method) error) error { if depth < 0 { return nil } structValue, err := TryDiscoverValueByKind(reflect.ValueOf(structOrItsType), reflect.Struct) if err != nil { structValue := reflect.ValueOf(structOrItsType) if !(structValue.Kind() == reflect.Interface) { return err } } structType := structValue.Type() if _, hasScan := scanned[structType]; hasScan { return nil } scanned[structType] = true for i := 0; i < structValue.NumField(); i++ { fieldType := structType.Field(i) if isExported := fieldType.PkgPath == ""; !isExported { continue } if !fieldType.Anonymous { continue } if !IsStruct(fieldType) { continue } if fieldStructType, err := TryDiscoverTypeByKind(fieldType, reflect.Struct); err == nil { fieldStruct := reflect.New(fieldStructType).Interface() if err = scanStructMethods(fieldStruct, scanned, depth-1, handler); err != nil { return err } } } structPtr, err := TryDiscoverValueByKind(reflect.ValueOf(structOrItsType), reflect.Ptr) if err != nil { return err } structTypePtr := structPtr.Type() for i := 0; i < structTypePtr.NumMethod(); i++ { method := structTypePtr.Method(i) if isExported := method.PkgPath == ""; !isExported { continue } if err := handler(method); err != nil { return err } } return nil } //StructField represents a struct field type StructField struct { Owner reflect.Value Value reflect.Value Type reflect.StructField } var onUnexportedHandler = IgnoreUnexportedFields //UnexportedFieldHandler represents unexported field handler type UnexportedFieldHandler func(structField *StructField) bool //Handler ignoring unexported fields func IgnoreUnexportedFields(structField *StructField) bool { return false } func SetUnexportedFieldHandler(handler UnexportedFieldHandler) error { if handler == nil { return errors.New("handler was nil") } onUnexportedHandler = handler return nil } //ProcessStruct reads passed in struct fields and values to pass it to provided handler func ProcessStruct(aStruct interface{}, handler func(fieldType reflect.StructField, field reflect.Value) error) error { structValue, err := TryDiscoverValueByKind(reflect.ValueOf(aStruct), reflect.Struct) if err != nil { return err } structType := structValue.Type() var fields = make(map[string]*StructField) for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if !fieldType.Anonymous { continue } field := structValue.Field(i) if !IsStruct(field) { fields[fieldType.Name] = &StructField{Type: fieldType, Value: field, Owner: structValue} continue } var aStruct interface{} if fieldType.Type.Kind() == reflect.Ptr { if field.IsNil() { if !field.CanSet() { continue } structValue.Field(i).Set(reflect.New(fieldType.Type.Elem())) } aStruct = field.Interface() } else { if field.CanAddr() { aStruct = field.Addr().Interface() } else if field.CanInterface() { aStruct = field.Interface() } else { continue } } if err := ProcessStruct(aStruct, func(fieldType reflect.StructField, field reflect.Value) error { structField := &StructField{Type: fieldType, Value: field, Owner: field} if field.CanAddr() { structField.Owner = field.Addr() } fields[fieldType.Name] = structField return nil }); err != nil { return err } } for i := 0; i < structType.NumField(); i++ { fieldType := structType.Field(i) if fieldType.Anonymous { continue } field := structValue.Field(i) structField := &StructField{Owner: structValue, Type: fieldType, Value: field} if isExported := fieldType.PkgPath == ""; !isExported { if !onUnexportedHandler(structField) { continue } } fields[fieldType.Name] = &StructField{Owner: structValue, Type: fieldType, Value: field} } for _, field := range fields { if err := handler(field.Type, field.Value); err != nil { return err } } return nil } //BuildTagMapping builds map keyed by mappedKeyTag tag value, and value is another map of keys where tag name is presents in the tags parameter. func BuildTagMapping(structTemplatePointer interface{}, mappedKeyTag string, resultExclusionTag string, inheritKeyFromField bool, convertKeyToLowerCase bool, tags []string) map[string](map[string]string) { reflectStructType := DiscoverTypeByKind(structTemplatePointer, reflect.Struct) var result = make(map[string]map[string]string) var anonymousMappings = make(map[string]map[string]string) for i := 0; i < reflectStructType.NumField(); i++ { var field reflect.StructField field = reflectStructType.Field(i) key := getTagValues(field, mappedKeyTag) if field.Anonymous && key == "" { var anonymousType = DereferenceType(field.Type) if anonymousType.Kind() == reflect.Struct { anonymousMapping := BuildTagMapping(reflect.New(anonymousType).Interface(), mappedKeyTag, resultExclusionTag, inheritKeyFromField, convertKeyToLowerCase, tags) for k, v := range anonymousMapping { anonymousMappings[k] = v anonymousMappings[k][anonymousKey] = "true" anonymousMappings[k][fieldIndexKey] = AsString(i) } } continue } isTransient := strings.EqualFold(field.Tag.Get(resultExclusionTag), "true") if isTransient { continue } if key == "" { if !inheritKeyFromField { continue } key = field.Name } if convertKeyToLowerCase { key = strings.ToLower(key) } result[key] = make(map[string]string) for _, tag := range tags { tagValue := field.Tag.Get(tag) if len(tagValue) > 0 { result[key][tag] = tagValue } } result[key][fieldNameKey] = field.Name } for k, v := range anonymousMappings { if _, has := result[k]; !has { result[k] = v } } return result } func getTagValues(field reflect.StructField, mappedKeyTag string) string { key := field.Tag.Get(mappedKeyTag) key = strings.Split(key, ",")[0] if mappedKeyTag == fieldNameKey { key = field.Name } return key } //NewFieldSettingByKey reads field's tags and returns them indexed by passed in key, fieldName is always part of the resulting map unless filed has "transient" tag. func NewFieldSettingByKey(aStruct interface{}, key string) map[string](map[string]string) { return BuildTagMapping(aStruct, key, "transient", true, true, columnMapping) } func setEmptyMap(source reflect.Value, dataTypes map[string]bool) { if !source.CanSet() { return } mapType := source.Type() mapPointer := reflect.New(mapType) mapValueType := mapType.Elem() mapKeyType := mapType.Key() newMap := mapPointer.Elem() newMap.Set(reflect.MakeMap(mapType)) targetMapKeyPointer := reflect.New(mapKeyType) targetMapValuePointer := reflect.New(mapValueType) var elementKey = targetMapKeyPointer.Elem() var elementValue = targetMapValuePointer.Elem() if elementValue.Kind() == reflect.Ptr && elementValue.IsNil() { component := reflect.New(elementValue.Type().Elem()) elementValue.Set(component) } if elementKey.Type() != mapKeyType { if elementKey.Type().AssignableTo(mapKeyType) { elementKey = elementKey.Convert(mapKeyType) } } if DereferenceType(elementValue.Type()).Kind() == reflect.Struct { initStruct(elementValue.Interface(), dataTypes) } newMap.SetMapIndex(elementKey, elementValue) var elem = mapPointer.Elem() source.Set(elem) } func createEmptySlice(source reflect.Value, dataTypes map[string]bool) { sliceType := DiscoverTypeByKind(source.Type(), reflect.Slice) if !source.CanSet() { return } slicePointer := reflect.New(sliceType) slice := slicePointer.Elem() componentType := DiscoverComponentType(sliceType) var targetComponentPointer = reflect.New(componentType) var targetComponent = targetComponentPointer.Elem() if DereferenceType(componentType).Kind() == reflect.Struct { componentType := targetComponent.Type() isPointer := componentType.Kind() == reflect.Ptr if isPointer { componentType = componentType.Elem() } structElement := reflect.New(componentType) initStruct(structElement.Interface(), dataTypes) if isPointer { targetComponentPointer.Elem().Set(structElement) } else { targetComponentPointer.Elem().Set(structElement.Elem()) } initStruct(targetComponentPointer.Elem().Interface(), dataTypes) } slice.Set(reflect.Append(slice, targetComponentPointer.Elem())) source.Set(slicePointer.Elem()) } //InitStruct initialise any struct pointer to empty struct func InitStruct(source interface{}) { var dataTypes = make(map[string]bool) if source == nil { return } initStruct(source, dataTypes) } func initStruct(source interface{}, dataTypes map[string]bool) { if source == nil { return } if !IsStruct(source) { return } var key = DereferenceType(source).Name() if _, has := dataTypes[key]; has { return } dataTypes[key] = true sourceValue, ok := source.(reflect.Value) if !ok { sourceValue = reflect.ValueOf(source) } if sourceValue.Type().Kind() == reflect.Ptr { elem := sourceValue.Elem() if elem.Kind() == reflect.Ptr && elem.IsNil() { return } if !sourceValue.Elem().IsValid() { return } } _ = ProcessStruct(source, func(fieldType reflect.StructField, fieldValue reflect.Value) error { if !fieldValue.CanInterface() { return nil } if fieldValue.Kind() == reflect.String && fieldValue.CanSet() { fieldValue.SetString(" ") return nil } if fieldType.Type.Kind() == reflect.Map { setEmptyMap(fieldValue, dataTypes) return nil } if fieldType.Type.Kind() == reflect.Slice { createEmptySlice(fieldValue, dataTypes) return nil } if fieldType.Type.Kind() != reflect.Ptr { return nil } if DereferenceType(fieldType).Kind() == reflect.Struct { if !fieldValue.CanSet() { return nil } if fieldValue.Type().Kind() == reflect.Ptr { fieldStruct := reflect.New(fieldValue.Type().Elem()) if reflect.TypeOf(source) != fieldStruct.Type() { initStruct(fieldStruct.Interface(), dataTypes) } fieldValue.Set(fieldStruct) } } return nil }) } //StructFieldMeta represents struct field meta type StructFieldMeta struct { Name string `json:"name,omitempty"` Type string `json:"type,omitempty"` Required bool `json:"required,"` Description string `json:"description,omitempty"` } //StructMeta represents struct meta details type StructMeta struct { Type string rawType reflect.Type `json:"-"` Fields []*StructFieldMeta `json:"fields,omitempty"` Dependencies []*StructMeta `json:"dependencies,omitempty"` } func (m *StructMeta) Message() map[string]interface{} { var result = make(map[string]interface{}) var deps = make(map[string]*StructMeta) for _, dep := range m.Dependencies { deps[dep.Type] = dep } for _, field := range m.Fields { if dep, ok := deps[field.Type]; ok { result[field.Name] = dep.Message() continue } result[field.Name] = "" } return result } //StructMetaFilter type StructMetaFilter func(field reflect.StructField) bool func DefaultStructMetaFilter(ield reflect.StructField) bool { return true } var structMetaFilter StructMetaFilter = DefaultStructMetaFilter //SetStructMetaFilter sets struct meta filter func SetStructMetaFilter(filter StructMetaFilter) error { if filter == nil { return errors.New("filter was nil") } structMetaFilter = filter return nil } //GetStructMeta returns struct meta func GetStructMeta(source interface{}) *StructMeta { var result = &StructMeta{} var trackedTypes = make(map[string]bool) getStructMeta(source, result, trackedTypes) return result } //InitStruct initialise any struct pointer to empty struct func getStructMeta(source interface{}, meta *StructMeta, trackedTypes map[string]bool) bool { if source == nil { return false } var structType = fmt.Sprintf("%T", source) if _, has := trackedTypes[structType]; has { return false } meta.Type = structType meta.Fields = make([]*StructFieldMeta, 0) meta.Dependencies = make([]*StructMeta, 0) sourceValue := reflect.ValueOf(source) if sourceValue.Kind() == reflect.Ptr { elem := sourceValue.Elem() if elem.Kind() == reflect.Ptr && elem.IsNil() { return false } if !sourceValue.Elem().IsValid() { source = reflect.New(sourceValue.Type().Elem()).Interface() } } meta.rawType = sourceValue.Type() trackedTypes[structType] = true _ = ProcessStruct(source, func(fieldType reflect.StructField, field reflect.Value) error { if !structMetaFilter(fieldType) { return nil } if isExported := fieldType.PkgPath == ""; !isExported { structField := &StructField{ Owner: reflect.ValueOf(source), Type: fieldType, Value: field, } if !onUnexportedHandler(structField) { return nil } field = structField.Value } if isJSONSkippable(string(fieldType.Tag)) { return nil } fieldMeta := &StructFieldMeta{} fieldMeta.Name = fieldType.Name fieldMeta.Type = fieldType.Type.Name() meta.Fields = append(meta.Fields, fieldMeta) if value, ok := fieldType.Tag.Lookup("required"); ok { fieldMeta.Required = AsBoolean(value) } if value, ok := fieldType.Tag.Lookup("description"); ok { fieldMeta.Description = value } var value = field.Interface() if value == nil { return nil } fieldMeta.Type = fmt.Sprintf("%T", value) if fieldType.PkgPath != "" { fieldMeta.Type = strings.Replace(fieldMeta.Type, "*", "", 1) } if IsStruct(value) { var fieldStruct = &StructMeta{} switch field.Kind() { case reflect.Ptr: var fieldValue interface{} if field.IsNil() { fieldValue = reflect.New(field.Type().Elem()).Interface() } else { fieldValue = field.Elem().Interface() } if getStructMeta(fieldValue, fieldStruct, trackedTypes) { meta.Dependencies = append(meta.Dependencies, fieldStruct) } case reflect.Struct: if field.CanInterface() { if getStructMeta(field.Interface(), fieldStruct, trackedTypes) { meta.Dependencies = append(meta.Dependencies, fieldStruct) } } } return nil } if IsMap(value) { var aMap = AsMap(field.Interface()) var mapValue interface{} for _, mapValue = range aMap { break } if mapValue != nil && IsStruct(mapValue) { var fieldStruct = &StructMeta{} if getStructMeta(mapValue, fieldStruct, trackedTypes) { meta.Dependencies = append(meta.Dependencies, fieldStruct) } } return nil } if IsSlice(value) { var aSlice = AsSlice(field.Interface()) if len(aSlice) > 0 { if aSlice[0] != nil && IsStruct(aSlice[0]) { var fieldStruct = &StructMeta{} if getStructMeta(aSlice[0], fieldStruct, trackedTypes) { meta.Dependencies = append(meta.Dependencies, fieldStruct) } } } return nil } return nil }) return true } func isJSONSkippable(tag string) bool { return strings.Contains(tag, "json:\"-") } //StructFields by name sorter type StructFields []*StructField // Len is part of sort.Interface. func (s StructFields) Len() int { return len(s) } // Swap is part of sort.Interface. func (s StructFields) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Less is part of sort.Interface. func (s StructFields) Less(i, j int) bool { return s[i].Type.Name < s[j].Type.Name } toolbox-0.33.2/struct_helper_test.go000066400000000000000000000114541374110251100175360ustar00rootroot00000000000000package toolbox_test import ( "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestProcessStruct(t *testing.T) { type Super struct { Parent int } type User struct { *Super Name string `column:"name"` DateOfBirth time.Time `column:"date" dateFormat:"2006-01-02 15:04:05.000000"` Id int `autogenrated:"true"` prv int Other string `transient:"true"` } user := User{Id: 1, Other: "!@#", Name: "foo", Super: &Super{12}} var userMap = make(map[string]interface{}) err := toolbox.ProcessStruct(&user, func(fieldType reflect.StructField, field reflect.Value) error { value := field.Interface() userMap[fieldType.Name] = value return nil }) assert.Nil(t, err) assert.Equal(t, 5, len(userMap)) assert.Equal(t, 12, userMap["Parent"]) assert.Equal(t, 1, userMap["Id"]) assert.Equal(t, "!@#", userMap["Other"]) } func TestBuildTagMapping(t *testing.T) { type User struct { Name string `column:"name"` DateOfBirth time.Time `column:"date" dateFormat:"2006-01-02 15:04:05.000000"` Id int `autogenrated:"true"` Other string `transient:"true"` } { tags := []string{"column", "autogenrated"} result := toolbox.BuildTagMapping((*User)(nil), "column", "transient", true, true, tags) { actual := len(result) expected := 3 assert.Equal(t, actual, expected, "Extract mapping count") } { actual, _ := result["name"]["fieldName"] expected := "Name" assert.Equal(t, actual, expected, "Extract name mapping") } { actual, _ := result["id"]["autogenrated"] expected := "true" assert.Equal(t, actual, expected, "Extract id flaged as autogenerated") } } { tags := []string{"column", "autogenrated"} result := toolbox.BuildTagMapping((*User)(nil), "fieldName", "transient", true, false, tags) actual, _ := result["Name"]["fieldName"] expected := "Name" assert.Equal(t, actual, expected, "Extract name mapping") } { tags := []string{"column", "autogenrated"} result := toolbox.BuildTagMapping((*User)(nil), "column", "transient", false, false, tags) { actual := len(result) expected := 2 assert.Equal(t, actual, expected, "Extract mapping count") } } type User2 struct { Name string `json:"name" column:"name"` DateOfBirth time.Time `json:"date" column:"date" dateFormat:"2006-01-02 15:04:05.000000"` Id int `json:"id" autogenrated:"true"` Other string `json:"other" transient:"true"` } { tags := []string{"column", "autogenrated"} result := toolbox.BuildTagMapping((*User)(nil), "column", "transient", true, true, tags) { actual := len(result) expected := 3 assert.Equal(t, actual, expected, "Extract mapping count") } { actual, _ := result["name"]["fieldName"] expected := "Name" assert.Equal(t, actual, expected, "Extract name mapping") } { actual, _ := result["id"]["autogenrated"] expected := "true" assert.Equal(t, actual, expected, "Extract id flaged as autogenerated") } } } func TestBuildEmbededStructTagMapping(t *testing.T) { type Super struct { Id int `autogenrated:"true"` Name string `column:"name"` } type User struct { *Super DateOfBirth time.Time `column:"date" dateFormat:"2006-01-02 15:04:05.000000"` Other string `transient:"true"` } tags := []string{"column", "autogenrated"} result := toolbox.BuildTagMapping((*User)(nil), "column", "transient", true, true, tags) { actual := len(result) expected := 3 assert.Equal(t, actual, expected, "Extract mapping count") } { actual, _ := result["name"]["fieldName"] expected := "Name" assert.Equal(t, actual, expected, "Extract name mapping") } { actual, _ := result["id"]["autogenrated"] expected := "true" assert.Equal(t, actual, expected, "Extract id flaged as autogenerated") } } type Type4 struct { Id int } type Type3 struct { Name map[string]string Type4 map[string]*Type4 } type Type2 struct { F1 int F3 *Type3 } type Type1 struct { F1 int F2 *Type2 F3 []interface{} F4 map[string]interface{} F5 []*Type3 } type SuperType1 struct { *Type1 } type SuperType2 struct { Type1 } func Test_InitStruct(t *testing.T) { { var t1 = &Type1{} toolbox.InitStruct(t1) assert.NotNil(t, t1.F2) assert.NotNil(t, t1.F3) assert.NotNil(t, t1.F4) assert.NotNil(t, t1.F5) } { var t1 = &SuperType1{} toolbox.InitStruct(t1) assert.NotNil(t, t1.F2) assert.NotNil(t, t1.F3) assert.NotNil(t, t1.F4) assert.NotNil(t, t1.F5) } { var t1 = &SuperType2{} toolbox.InitStruct(t1) assert.NotNil(t, t1.F2) assert.NotNil(t, t1.F3) assert.NotNil(t, t1.F4) assert.NotNil(t, t1.F5) } } func Test_GetStructMeta(t *testing.T) { var t1 = &Type1{} toolbox.InitStruct(t1) meta := toolbox.GetStructMeta(t1) assert.NotNil(t, meta) } toolbox-0.33.2/test/000077500000000000000000000000001374110251100142375ustar00rootroot00000000000000toolbox-0.33.2/test/config.json000066400000000000000000000000501374110251100163720ustar00rootroot00000000000000{ "Attr1":"value1", "Attr2":"value2" }toolbox-0.33.2/test/corrupted_config.json000066400000000000000000000000611374110251100204630ustar00rootroot00000000000000{ "Attr1":"value1", "Attr2":"value2" "attr"; }toolbox-0.33.2/test/fileset_info/000077500000000000000000000000001374110251100167055ustar00rootroot00000000000000toolbox-0.33.2/test/fileset_info/user.go000066400000000000000000000015011374110251100202070ustar00rootroot00000000000000package fileset_info import ( "fmt" "time" ) type Addresses []Address type Ints []int type Z string type Country struct { Code string Name string } //Address represents a test struct type Address struct { Country City string } type A Address //User represents a test struct type User struct { //my comments ///abc comment ID *int // comment1 type Name string DateOfBirth time.Time `foo="bar"` Address Address AddressPointer *Address Addresses Addresses Ints []int Ints2 Ints M map[string][]string C chan *bool Appointments []time.Time } //Test represents a test method func (u User) Test() { fmt.Printf("Abc %v", u) } //Test1 represents a test method func (u User) Test1() bool { fmt.Printf("Abc %v", u) return false } toolbox-0.33.2/test/fileset_info/user_func.go000066400000000000000000000001701374110251100212230ustar00rootroot00000000000000package fileset_info import "fmt" //Test represents a test method func (u *User) Test2() { fmt.Printf("Abc %v", u) } toolbox-0.33.2/text.go000066400000000000000000000076251374110251100146050ustar00rootroot00000000000000package toolbox import ( "bufio" "io" "unicode" ) //IsASCIIText return true if supplied string does not have binary data func IsASCIIText(candidate string) bool { for _, r := range candidate { if r == '\n' || r == '\r' || r == '\t' { continue } if r > unicode.MaxASCII || !unicode.IsPrint(r) || r == '`' { return false } } return true } //IsPrintText return true if all candidate characters are printable (unicode.IsPrintText) func IsPrintText(candidate string) bool { for _, r := range candidate { if !unicode.IsPrint(r) { if r == '\n' || r == '\r' || r == '\t' || r == '`' { continue } return false } } return true } //TerminatedSplitN split supplied text into n fragmentCount, each terminated with supplied terminator func TerminatedSplitN(text string, fragmentCount int, terminator string) []string { var result = make([]string, 0) if fragmentCount == 0 { fragmentCount = 1 } fragmentSize := len(text) / fragmentCount lowerBound := 0 for i := fragmentSize - 1; i < len(text); i++ { isLast := i+1 == len(text) isAtLeastOfFragmentSize := i-lowerBound >= fragmentSize isNewLine := string(text[i:i+len(terminator)]) == terminator if (isAtLeastOfFragmentSize && isNewLine) || isLast { result = append(result, string(text[lowerBound:i+1])) lowerBound = i + 1 } } return result } //SplitTextStream divides reader supplied text by number of specified line func SplitTextStream(reader io.Reader, writerProvider func() io.WriteCloser, elementCount int) error { scanner := bufio.NewScanner(reader) var writer io.WriteCloser counter := 0 var err error if elementCount == 0 { elementCount = 1 } for scanner.Scan() { if writer == nil { writer = writerProvider() } data := scanner.Bytes() if err = scanner.Err(); err != nil { return err } if counter > 0 { if _, err = writer.Write([]byte{'\n'}); err != nil { return err } } if _, err = writer.Write(data); err != nil { return err } counter++ if counter == elementCount { if err := writer.Close(); err != nil { return err } counter = 0 writer = nil } } if writer != nil { return writer.Close() } return nil } const ( CaseUpper = iota CaseLower CaseUpperCamel CaseLowerCamel CaseUpperUnderscore CaseLowerUnderscore ) //ToCaseFormat format text, from, to are const: CaseLower, CaseUpperCamel, CaseLowerCamel, CaseUpperUnderscore, CaseLowerUnderscore, func ToCaseFormat(text string, from, to int) string { toUpper := false toLower := false toCamel := false toUnserscore := false fromCamel := false fromUnserscore := false switch to { case CaseUpper, CaseUpperUnderscore: toUpper = true case CaseLower, CaseLowerUnderscore: toLower = true case CaseUpperCamel, CaseLowerCamel: toCamel = true } switch to { case CaseUpperUnderscore, CaseLowerUnderscore: toUnserscore = true } switch from { case CaseUpperCamel, CaseLowerCamel: fromCamel = true case CaseUpperUnderscore, CaseLowerUnderscore: fromUnserscore = true } underscore := rune('_') var result = make([]rune, 0) makeLower := false makeUpper := false hasUnderscore := false for i, r := range text { first := i == 0 if toUpper { makeUpper = true } else if toLower { makeLower = true } if first { if to == CaseLowerCamel { r = unicode.ToLower(r) } else if to == CaseUpperCamel { r = unicode.ToUpper(r) } } else { if fromUnserscore { if toCamel { if r == underscore { hasUnderscore = true continue } if hasUnderscore { makeUpper = true hasUnderscore = false } else { makeLower = true } } } if unicode.IsUpper(r) && fromCamel { if toUnserscore { result = append(result, underscore) } } } if makeLower { r = unicode.ToLower(r) } else if makeUpper { r = unicode.ToUpper(r) } result = append(result, r) makeUpper = false makeLower = false } return string(result) } toolbox-0.33.2/text_test.go000066400000000000000000000152641374110251100156420ustar00rootroot00000000000000package toolbox import ( "bytes" "fmt" "github.com/stretchr/testify/assert" "io" "strings" "testing" ) func TestIsASCIIText(t *testing.T) { var useCases = []struct { Description string Candidate string Expected bool }{ { Description: "basic text", Candidate: `abc`, Expected: true, }, { Description: "JSON object like text", Candidate: `{"k1"}`, Expected: true, }, { Description: "JSON array like text", Candidate: `["$k1"]`, Expected: true, }, { Description: "bin data", Candidate: "\u0000", Expected: false, }, { Description: "JSON text", Candidate: `{ "RepositoryDatastore":"db1", "Db": [ { "Name": "db1", "Config": { "PoolSize": 3, "MaxPoolSize": 5, "DriverName": "mysql", "Descriptor": "[username]:[password]@tcp(127.0.0.1:3306)/db1?parseTime=true", "Credentials": "$mysqlCredentials" } } ] } `, Expected: true, }, } for _, useCase := range useCases { assert.EqualValues(t, useCase.Expected, IsASCIIText(useCase.Candidate), useCase.Description) } } func TestIsPrintText(t *testing.T) { var useCases = []struct { Description string Candidate string Expected bool }{ { Description: "basic text", Candidate: `abc`, Expected: true, }, { Description: "JSON object like text", Candidate: `{"k1"}`, Expected: true, }, { Description: "JSON array like text", Candidate: `["$k1"]`, Expected: true, }, { Description: "bin data", Candidate: "\u0000", Expected: false, }, { Description: "JSON text", Candidate: `{ "RepositoryDatastore":"db1", "Db": [ { "Name": "db1", "Config": { "PoolSize": 3, "MaxPoolSize": 5, "DriverName": "mysql", "Descriptor": "[username]:[password]@tcp(127.0.0.1:3306)/db1?parseTime=true", "Credentials": "mysql" } } ] } `, Expected: true, }, } for _, useCase := range useCases { assert.EqualValues(t, useCase.Expected, IsPrintText(useCase.Candidate), useCase.Description) } } func TestTerminatedSplitN(t *testing.T) { var data = make([]byte, 0) for i := 0; i < 9; i++ { data = append(data, []byte(fmt.Sprintf("%v %v\n", strings.Repeat("x", 32), i))...) } text := string(data) useCases := []struct { description string fragmentCount int expectedFragmentSizes []int }{ { description: "one fragment case", fragmentCount: 1, expectedFragmentSizes: []int{len(data)}, }, { description: "two fragments case", fragmentCount: 2, expectedFragmentSizes: []int{175, 140}, }, { description: "3 fragments case", fragmentCount: 3, expectedFragmentSizes: []int{140, 140, 35}, }, { description: "7 fragments case", fragmentCount: 7, expectedFragmentSizes: []int{70, 70, 70, 70, 35}, }, { description: "10 fragments case", //no more fragments then lines, so only 9 fragments here fragmentCount: 10, expectedFragmentSizes: []int{35, 35, 35, 35, 35, 35, 35, 35, 35}, }, } for _, useCase := range useCases { fragments := TerminatedSplitN(text, useCase.fragmentCount, "\n") var actualFragmentSizes = make([]int, len(fragments)) for i, fragment := range fragments { actualFragmentSizes[i] = len(fragment) } assert.EqualValues(t, useCase.expectedFragmentSizes, actualFragmentSizes, useCase.description) } } type testWriter struct { *bytes.Buffer data *[]string } func (t *testWriter) Close() error { *t.data = append(*t.data, t.String()) return nil } func newTestWriter(data *[]string) io.WriteCloser { return &testWriter{ data: data, Buffer: new(bytes.Buffer), } } func TestSplitTextStream(t *testing.T) { var data = make([]byte, 0) for i := 0; i < 9; i++ { data = append(data, []byte(fmt.Sprintf("%v %v\n", strings.Repeat("x", 2), i))...) } text := string(data) useCases := []struct { description string elements int expect []string }{ { description: "no more then 4 lines case", elements: 4, expect: []string{ "xx 0\nxx 1\nxx 2\nxx 3", "xx 4\nxx 5\nxx 6\nxx 7", "xx 8", }, }, { description: "3 elements each", elements: 3, expect: []string{ "xx 0\nxx 1\nxx 2", "xx 3\nxx 4\nxx 5", "xx 6\nxx 7\nxx 8", }, }, { description: "9 elements", elements: 1, expect: []string{ "xx 0", "xx 1", "xx 2", "xx 3", "xx 4", "xx 5", "xx 6", "xx 7", "xx 8", }, }, { description: "9 elements", elements: 0, expect: []string{ "xx 0", "xx 1", "xx 2", "xx 3", "xx 4", "xx 5", "xx 6", "xx 7", "xx 8", }, }, { description: "1 elements", elements: 10, expect: []string{ "xx 0\nxx 1\nxx 2\nxx 3\nxx 4\nxx 5\nxx 6\nxx 7\nxx 8", }, }, { description: "1 elements", elements: 9, expect: []string{ "xx 0\nxx 1\nxx 2\nxx 3\nxx 4\nxx 5\nxx 6\nxx 7\nxx 8", }, }, } for _, useCase := range useCases { var data = make([]string, 0) err := SplitTextStream(strings.NewReader(text), func() io.WriteCloser { return newTestWriter(&data) }, useCase.elements) assert.Nil(t, err) assert.EqualValues(t, useCase.expect, data, useCase.description) } } func Test_CaseFormat(t *testing.T) { var useCases = []struct { description string caseFrom int caseTo int input string expect string }{ { description: "camel to uppercase", input: "thisIsMyTest", caseFrom: CaseLowerCamel, caseTo: CaseUpper, expect: "THISISMYTEST", }, { description: "camel to lower underscore", input: "thisIsMyTest", caseFrom: CaseLowerCamel, caseTo: CaseLowerUnderscore, expect: "this_is_my_test", }, { description: "camel to upper underscore", input: "thisIsMyTest", caseFrom: CaseLowerCamel, caseTo: CaseUpperUnderscore, expect: "THIS_IS_MY_TEST", }, { description: "lower underscore to upper camel", input: "this_is_my_test", caseFrom: CaseLowerUnderscore, caseTo: CaseUpperCamel, expect: "ThisIsMyTest", }, { description: "upper underscore to lower camel", input: "THIS_IS_MY_TEST", caseFrom: CaseUpperUnderscore, caseTo: CaseLowerCamel, expect: "thisIsMyTest", }, { description: "upper camel to lower camel", input: "ThisIsMyTest", caseFrom: CaseUpperCamel, caseTo: CaseLowerCamel, expect: "thisIsMyTest", }, } for _, useCase := range useCases { actual := ToCaseFormat(useCase.input, useCase.caseFrom, useCase.caseTo) assert.Equal(t, useCase.expect, actual, useCase.description) } } toolbox-0.33.2/time_format.go000066400000000000000000000071061374110251100161210ustar00rootroot00000000000000package toolbox import ( "strings" "time" ) //DateFormatKeyword constant 'dateFormat' key var DateFormatKeyword = "dateFormat" //DateLayoutKeyword constant 'dateLayout' key var DateLayoutKeyword = "dateLayout" //DateFormatToLayout converts java date format https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html#rfc822timezone into go date layout func DateFormatToLayout(dateFormat string) string { dateFormat = strings.Replace(dateFormat, "ddd", "_2", 1) dateFormat = strings.Replace(dateFormat, "dd", "02", 1) dateFormat = strings.Replace(dateFormat, "d", "2", 1) dateFormat = strings.Replace(dateFormat, "HH", "15", 1) dateFormat = strings.Replace(dateFormat, "hh", "03", 1) dateFormat = strings.Replace(dateFormat, "h", "3", 1) dateFormat = strings.Replace(dateFormat, "mm", "04", 1) dateFormat = strings.Replace(dateFormat, "m", "4", 1) dateFormat = strings.Replace(dateFormat, "ss", "05", 1) dateFormat = strings.Replace(dateFormat, "s", "5", 1) dateFormat = strings.Replace(dateFormat, "yyyy", "2006", 1) dateFormat = strings.Replace(dateFormat, "yy", "06", 1) dateFormat = strings.Replace(dateFormat, "y", "06", 1) dateFormat = strings.Replace(dateFormat, "SSS", "000", 1) dateFormat = strings.Replace(dateFormat, "a", "pm", 1) dateFormat = strings.Replace(dateFormat, "aa", "PM", 1) dateFormat = strings.Replace(dateFormat, "MMMM", "January", 1) dateFormat = strings.Replace(dateFormat, "MMM", "Jan", 1) dateFormat = strings.Replace(dateFormat, "MM", "01", 1) dateFormat = strings.Replace(dateFormat, "M", "1", 1) dateFormat = strings.Replace(dateFormat, "ZZ", "-0700", 1) dateFormat = strings.Replace(dateFormat, "Z", "-07", 1) dateFormat = strings.Replace(dateFormat, "zz:zz", "Z07:00", 1) dateFormat = strings.Replace(dateFormat, "zzzz", "Z0700", 1) dateFormat = strings.Replace(dateFormat, "z", "MST", 1) dateFormat = strings.Replace(dateFormat, "EEEE", "Monday", 1) dateFormat = strings.Replace(dateFormat, "E", "Mon", 1) return dateFormat } //GetTimeLayout returns time laout from passed in map, first it check if DateLayoutKeyword is defined is so it returns it, otherwise it check DateFormatKeyword and if exists converts it to dateLayout //If neithers keys exists it panics, please use HasTimeLayout to avoid panic func GetTimeLayout(input interface{}) string { switch settings := input.(type) { case map[string]string: if value, found := settings[DateLayoutKeyword]; found { return value } if value, found := settings[DateFormatKeyword]; found { return DateFormatToLayout(value) } case map[string]interface{}: if value, found := settings[DateLayoutKeyword]; found { return AsString(value) } if value, found := settings[DateFormatKeyword]; found { return DateFormatToLayout(AsString(value)) } } return "" } //HasTimeLayout checks if dateLayout can be taken from the passed in setting map func HasTimeLayout(input interface{}) bool { switch settings := input.(type) { case map[string]string: if _, found := settings[DateLayoutKeyword]; found { return true } if _, found := settings[DateFormatKeyword]; found { return true } case map[string]interface{}: if _, found := settings[DateLayoutKeyword]; found { return true } if _, found := settings[DateFormatKeyword]; found { return true } } return false } //TimestampToString formats timestamp to passed in java style date format func TimestampToString(dateFormat string, unixTimestamp, unixNanoTimestamp int64) string { t := time.Unix(unixTimestamp, unixNanoTimestamp) dateLayout := DateFormatToLayout(dateFormat) return t.Format(dateLayout) } toolbox-0.33.2/time_format_test.go000066400000000000000000000072221374110251100171570ustar00rootroot00000000000000package toolbox_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "strings" "testing" "time" ) func TestTimeFormat(t *testing.T) { { timeLayout := toolbox.DateFormatToLayout("yyyy/MM/dd/hh") fmt.Printf("%s\n", timeLayout) } { timeLayout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss z") timeValue, err := time.Parse(timeLayout, "2018-01-15 08:02:23 UTC") assert.Nil(t, err) assert.EqualValues(t, 23, timeValue.Second()) } { timeLayout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss") timeValue, err := time.Parse(timeLayout, "2016-03-01 03:10:11") assert.Nil(t, err) assert.EqualValues(t, 11, timeValue.Second()) } { dateLayout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss.SSSZ") timeValue, err := time.Parse(dateLayout, "2022-11-10 10:32:28.984-08") assert.Nil(t, err) assert.Equal(t, int64(1668105148), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss.SSSZ") timeValue, err := time.Parse(dateLaout, "2022-11-10 10:32:28.984-08") assert.Nil(t, err) assert.Equal(t, int64(1668105148), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss.SSS") timeValue, err := time.Parse(dateLaout, "2017-11-04 22:29:33.363") assert.Nil(t, err) assert.Equal(t, 2017, timeValue.Year()) assert.Equal(t, time.Month(11), timeValue.Month()) assert.Equal(t, 4, timeValue.Day()) assert.Equal(t, int64(1509834573), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("dd/MM/yyyy hh:mm:ss") timeValue, err := time.Parse(dateLaout, "22/02/2016 12:32:01") assert.Nil(t, err) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyyMMdd hh:mm:ss") timeValue, err := time.Parse(dateLaout, "20160222 12:32:01") assert.Nil(t, err) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z") timeValue, err := time.Parse(dateLaout, "2016-02-22 12:32:01 UTC") assert.Nil(t, err) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z") timeValue, err := time.Parse(dateLaout, "2016-02-22 12:32:01 UTC") assert.Nil(t, err) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z") timeValue, err := time.Parse(dateLaout, "2016-02-22 12:32:01 UTC") assert.Nil(t, err) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd HH:mm:ss z") timeValue, err := time.Parse(dateLaout, "2016-06-02 21:46:19 UTC") assert.Nil(t, err) assert.Equal(t, int64(1464903979), timeValue.Unix()) } } func TestGetTimeLayout(t *testing.T) { { settings := map[string]string{ toolbox.DateFormatKeyword: "yyyy-MM-dd HH:mm:ss z", } assert.Equal(t, "2006-01-02 15:04:05 MST", toolbox.GetTimeLayout(settings)) assert.True(t, toolbox.HasTimeLayout(settings)) } { settings := map[string]string{ toolbox.DateLayoutKeyword: "2006-1-02 15:04:05 MST", } assert.Equal(t, "2006-1-02 15:04:05 MST", toolbox.GetTimeLayout(settings)) assert.True(t, toolbox.HasTimeLayout(settings)) } { settings := map[string]string{} assert.False(t, toolbox.HasTimeLayout(settings)) } } func TestTimestampToString(t *testing.T) { { date := toolbox.TimestampToString("yyyy-MM-dd HH:mm:ss z", int64(0), 1480435743722684356) assert.True(t, strings.Contains(date, "2016-11")) } { date := toolbox.TimestampToString("yyyyMMddhh", int64(0), 1489512277722684356) assert.True(t, strings.Contains(date, "201703")) } } toolbox-0.33.2/time_helper.go000066400000000000000000000262621374110251100161140ustar00rootroot00000000000000package toolbox import ( "fmt" "log" "strings" "time" ) const ( Now = "now" Tomorrow = "tomorrow" Yesterday = "yesterday" //TimeAtTwoHoursAgo = "2hoursAgo" //TimeAtHourAhead = "hourAhead" //TimeAtTwoHoursAhead = "2hoursAhead" DurationWeek = "week" DurationDay = "day" DurationHour = "hour" DurationMinute = "minute" DurationMinuteAbbr = "min" DurationSecond = "second" DurationSecondAbbr = "sec" DurationMillisecond = "millisecond" DurationMillisecondAbbr = "ms" DurationMicrosecond = "microsecond" DurationMicrosecondAbbr = "us" DurationNanosecond = "nanosecond" DurationNanosecondAbbr = "ns" ) //AtTime represents a time at given schedule type AtTime struct { WeekDay string Hour string Minute string TZ string loc *time.Location } func (t *AtTime) min(base time.Time) int { switch t.Minute { case "*": return (base.Minute() + 1) % 59 case "": return 0 } candidates := strings.Split(t.Minute, ",") for _, candidate := range candidates { candidateMin := AsInt(candidate) if base.Minute() < candidateMin { return candidateMin } } return AsInt(candidates[0]) } func (t *AtTime) hour(base time.Time) int { min := t.min(base) switch t.Hour { case "*": if min > base.Minute() { return base.Hour() } return (base.Hour() + 1) % 23 case "": return 0 } candidates := strings.Split(t.Hour, ",") for _, candidate := range candidates { candidateHour := AsInt(candidate) if base.Hour() < candidateHour { return candidateHour } } return AsInt(candidates[0]) } func (t *AtTime) weekday(base time.Time) int { hour := t.hour(base) min := t.min(base) baseWeekday := int(base.Weekday()) isPastDue := hour > base.Hour() || (hour == base.Hour() && min > base.Minute()) switch t.WeekDay { case "*": if isPastDue { return baseWeekday } return (baseWeekday + 1) % 7 case "": return 0 } candidates := strings.Split(t.WeekDay, ",") result := AsInt(candidates[0]) for _, candidate := range candidates { candidateWeekday := AsInt(candidate) if baseWeekday < candidateWeekday { result = candidateWeekday break } } if result < baseWeekday && isPastDue { return 7 + result } return result } //Init initializes tz func (t *AtTime) Init() error { if t.TZ == "" { return nil } var err error t.loc, err = time.LoadLocation(t.TZ) return err } //Next returns next time schedule func (t *AtTime) Next(base time.Time) time.Time { if t.loc != nil && base.Location() != nil && base.Location() != t.loc { base = base.In(t.loc) } else { t.loc = base.Location() } min := t.min(base) hour := t.hour(base) timeLiteral := base.Format("2006-01-02") updateTimeLiteral := fmt.Sprintf("%v %02d:%02d:00", timeLiteral, hour, min) weekday := t.weekday(base) baseWeekday := int(base.Weekday()) weekdayDiff := 0 if weekday >= baseWeekday { weekdayDiff = weekday - baseWeekday } else { weekdayDiff = 7 + weekday - baseWeekday } var result time.Time if t.loc != nil { result, _ = time.ParseInLocation("2006-01-02 15:04:05", updateTimeLiteral, t.loc) } else { result, _ = time.Parse("2006-01-02 15:04:05", updateTimeLiteral) } if weekdayDiff > 0 { result = result.Add(time.Hour * 24 * time.Duration(weekdayDiff)) } else if weekdayDiff == 0 && AsInt(t.WeekDay) > 0 { result = result.Add(time.Hour * 24 * 7) } if result.UnixNano() < base.UnixNano() { log.Printf("invalid schedule next: %v is before base: %v\n", result, base) } return result } //Duration represents duration type Duration struct { Value int Unit string } //Duration return durations func (d Duration) Duration() (time.Duration, error) { return NewDuration(d.Value, d.Unit) } //NewDuration returns a durationToken for supplied value and time unit, 3, "second" func NewDuration(value int, unit string) (time.Duration, error) { var duration time.Duration switch unit { case DurationWeek: duration = time.Hour * 24 * 7 case DurationDay: duration = time.Hour * 24 case DurationHour: duration = time.Hour case DurationMinute, DurationMinuteAbbr: duration = time.Minute case DurationSecond, DurationSecondAbbr: duration = time.Second case DurationMillisecond, DurationMillisecondAbbr: duration = time.Millisecond case DurationMicrosecond, DurationMicrosecondAbbr: duration = time.Microsecond case DurationNanosecond, DurationNanosecondAbbr: duration = time.Nanosecond default: return 0, fmt.Errorf("unsupported unit: %v", unit) } return time.Duration(value) * duration, nil } const ( eofToken = -1 invalidToken = iota timeValueToken nowToken yesterdayToken tomorrowToken whitespacesToken durationToken inTimezoneToken durationPluralToken positiveModifierToken negativeModifierToken timezoneToken ) var timeAtExpressionMatchers = map[int]Matcher{ timeValueToken: NewIntMatcher(), whitespacesToken: CharactersMatcher{" \n\t"}, durationToken: NewKeywordsMatcher(false, DurationWeek, DurationDay, DurationHour, DurationMinute, DurationMinuteAbbr, DurationSecond, DurationSecondAbbr, DurationMillisecond, DurationMillisecondAbbr, DurationMicrosecond, DurationMicrosecondAbbr, DurationNanosecond, DurationNanosecondAbbr), durationPluralToken: NewKeywordsMatcher(false, "s"), nowToken: NewKeywordsMatcher(false, Now), yesterdayToken: NewKeywordsMatcher(false, Yesterday), tomorrowToken: NewKeywordsMatcher(false, Tomorrow), positiveModifierToken: NewKeywordsMatcher(false, "onward", "ahead", "after", "later", "in the future", "inthefuture"), negativeModifierToken: NewKeywordsMatcher(false, "past", "ago", "before", "earlier", "in the past", "inthepast"), inTimezoneToken: NewKeywordsMatcher(false, "in"), timezoneToken: NewRemainingSequenceMatcher(), eofToken: &EOFMatcher{}, } //TimeAt returns time of now supplied offsetExpression, this function uses TimeDiff func TimeAt(offsetExpression string) (*time.Time, error) { return TimeDiff(time.Now(), offsetExpression) } //TimeDiff returns time for supplied base time and literal, the supported literal now, yesterday, tomorrow, or the following template: // - [timeValueToken] durationToken past_or_future_modifier [IN tz] // where time modifier can be any of the following: "onward", "ahead", "after", "later", or "past", "ago", "before", "earlier", "in the future", "in the past") ) func TimeDiff(base time.Time, expression string) (*time.Time, error) { if expression == "" { return nil, fmt.Errorf("expression was empty") } var delta time.Duration var isNegative = false tokenizer := NewTokenizer(expression, invalidToken, eofToken, timeAtExpressionMatchers) var val = 1 var isTimeExtracted = false token, err := ExpectToken(tokenizer, "", timeValueToken, nowToken, yesterdayToken, tomorrowToken) if err == nil { switch token.Token { case timeValueToken: val, _ = ToInt(token.Matched) case yesterdayToken: isNegative = true fallthrough case tomorrowToken: delta, _ = NewDuration(1, DurationDay) fallthrough case nowToken: isTimeExtracted = true } } if !isTimeExtracted { token, err = ExpectTokenOptionallyFollowedBy(tokenizer, whitespacesToken, "expected time unit", durationToken) if err != nil { return nil, err } delta, _ = NewDuration(val, strings.ToLower(token.Matched)) _, _ = ExpectToken(tokenizer, "", durationPluralToken) token, err = ExpectTokenOptionallyFollowedBy(tokenizer, whitespacesToken, "expected time modifier", positiveModifierToken, negativeModifierToken) if err != nil { return nil, err } if token.Token == negativeModifierToken { isNegative = true } } if token, err = ExpectTokenOptionallyFollowedBy(tokenizer, whitespacesToken, "expected in", inTimezoneToken); err == nil { token, err = ExpectToken(tokenizer, "epected timezone", timezoneToken) if err != nil { return nil, err } tz := strings.TrimSpace(token.Matched) tzLocation, err := time.LoadLocation(tz) if err != nil { return nil, fmt.Errorf("failed to load timezone tzLocation: %v, %v", tz, err) } base = base.In(tzLocation) } token, err = ExpectToken(tokenizer, "expected eofToken", eofToken) if err != nil { return nil, err } if isNegative { delta *= -1 } base = base.Add(delta) return &base, nil } //ElapsedToday returns elapsed today time percent, it takes optionally timezone func ElapsedToday(tz string) (float64, error) { if tz != "" { tz = "In" + tz } now, err := TimeAt("now" + tz) if err != nil { return 0, err } return ElapsedDay(*now), nil } //ElapsedDay returns elapsed pct for passed in day (second elapsed that day over 24 hours) func ElapsedDay(dateTime time.Time) float64 { elapsedToday := time.Duration(dateTime.Hour())*time.Hour + time.Duration(dateTime.Minute())*time.Minute + time.Duration(dateTime.Second()) + time.Second elapsedTodayPct := float64(elapsedToday) / float64((24 * time.Hour)) return elapsedTodayPct } //RemainingToday returns remaining today time percent, it takes optionally timezone func RemainingToday(tz string) (float64, error) { elapsedToday, err := ElapsedToday(tz) if err != nil { return 0, err } return 1.0 - elapsedToday, nil } //TimeWindow represents a time window type TimeWindow struct { Loopback *Duration StartDate string startTime *time.Time EndDate string endTime *time.Time TimeLayout string TimeFormat string Interval *Duration } //Range iterates with interval step between start and window end. func (w *TimeWindow) Range(handler func(time time.Time) (bool, error)) error { start, err := w.StartTime() if err != nil { return err } end, err := w.EndTime() if err != nil { return err } if w.Interval == nil && w.Loopback != nil { w.Interval = w.Loopback } if w.Interval == nil { _, err = handler(*end) return err } interval, err := w.Interval.Duration() if err != nil { return err } for ts := *start; ts.Before(*end) || ts.Equal(*end); ts = ts.Add(interval) { if ok, err := handler(ts); err != nil || !ok { return err } } return err } //Layout return time layout func (w *TimeWindow) Layout() string { if w.TimeLayout != "" { return w.TimeLayout } if w.TimeFormat != "" { w.TimeLayout = DateFormatToLayout(w.TimeFormat) } if w.TimeLayout == "" { w.TimeLayout = time.RFC3339 } return w.TimeLayout } //StartTime returns time window start time func (w *TimeWindow) StartTime() (*time.Time, error) { if w.StartDate != "" { if w.startTime != nil { return w.startTime, nil } timeLayout := w.Layout() startTime, err := time.Parse(timeLayout, w.StartDate) if err != nil { return nil, err } w.startTime = &startTime return w.startTime, nil } endDate, err := w.EndTime() if err != nil { return nil, err } if w.Loopback == nil || w.Loopback.Value == 0 { return endDate, nil } loopback, err := w.Loopback.Duration() if err != nil { return nil, err } startTime := endDate.Add(-loopback) return &startTime, nil } //EndTime returns time window end time func (w *TimeWindow) EndTime() (*time.Time, error) { if w.EndDate != "" { if w.endTime != nil { return w.endTime, nil } timeLayout := w.Layout() endTime, err := time.Parse(timeLayout, w.EndDate) if err != nil { return nil, err } w.endTime = &endTime return w.endTime, nil } now := time.Now() return &now, nil } toolbox-0.33.2/time_helper_test.go000066400000000000000000000261411374110251100171470ustar00rootroot00000000000000package toolbox import ( "github.com/stretchr/testify/assert" "math" "testing" "time" ) func TestAtTime_Next(t *testing.T) { timeLayout := "2006-01-02 15:04:05" var useCases = []struct { description string at *AtTime baseTime string expectTime string }{ { description: "evey 1/2 - next day", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "30", }, baseTime: "2019-01-01 23:33:01", expectTime: "2019-01-02 01:30:00", }, { description: "evey 1/2, last minute - next day", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "0,30", }, baseTime: "2019-01-01 23:59:00", expectTime: "2019-01-02 01:00:00", }, { description: "evey 1/2, last day of the month - next day", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "0,30", }, baseTime: "2019-05-31 23:59:00", expectTime: "2019-06-01 01:00:00", }, { description: "evey hour - next day", at: &AtTime{ WeekDay: "*", Hour: "0", Minute: "*", }, baseTime: "2019-01-01 23:01:01", expectTime: "2019-01-02 00:02:00", }, { description: "evey minute", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "*", }, baseTime: "2019-01-01 01:01:01", expectTime: "2019-01-01 01:02:00", }, { description: "evey 30 minute before", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "30", }, baseTime: "2019-01-01 01:01:01", expectTime: "2019-01-01 01:30:00", }, { description: "evey 30 minute after", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "30", }, baseTime: "2019-01-01 01:31:01", expectTime: "2019-01-01 02:30:00", }, { description: "evey 10, 30 minute, before", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "10,30", }, baseTime: "2019-01-01 01:09:01", expectTime: "2019-01-01 01:10:00", }, { description: "evey 10, 30 minute, after first", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "10,30", }, baseTime: "2019-01-01 01:13:01", expectTime: "2019-01-01 01:30:00", }, { description: "evey 10, 30 minute, after second", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "10,30", }, baseTime: "2019-01-01 01:33:01", expectTime: "2019-01-01 02:10:00", }, { description: "evey *:0 minute", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "0", }, baseTime: "2019-01-01 01:59:01", expectTime: "2019-01-01 02:00:00", }, { description: "evey hour", at: &AtTime{ WeekDay: "*", Hour: "*", Minute: "", }, baseTime: "2019-01-01 01:33:01", expectTime: "2019-01-01 02:00:00", }, { description: "at 13 hour, before", at: &AtTime{ WeekDay: "*", Hour: "13", Minute: "", }, baseTime: "2019-01-01 01:33:01", expectTime: "2019-01-01 13:00:00", }, { description: "at 13 hour, after", at: &AtTime{ WeekDay: "*", Hour: "13", Minute: "", }, baseTime: "2019-01-01 15:33:01", expectTime: "2019-01-02 13:00:00", }, { description: "at midnight", at: &AtTime{ WeekDay: "*", Hour: "0", Minute: "", }, baseTime: "2019-01-01 23:33:01", expectTime: "2019-01-02 00:00:00", }, { description: "at midnight weekday 9", at: &AtTime{ WeekDay: "*", Hour: "0", Minute: "", }, baseTime: "2019-01-06 23:33:01", expectTime: "2019-01-07 00:00:00", }, { description: "every 0 weekday", at: &AtTime{ WeekDay: "0", Hour: "", Minute: "", }, baseTime: "2019-01-04 23:33:01", expectTime: "2019-01-06 00:00:00", }, { description: "every 2 weekday", at: &AtTime{ WeekDay: "2", Hour: "", Minute: "", }, baseTime: "2019-01-04 23:33:01", expectTime: "2019-01-08 00:00:00", }, { description: "every 2nd weekday - overlaps with base time", at: &AtTime{ WeekDay: "2", Hour: "", Minute: "", }, baseTime: "2019-01-08 23:33:01", expectTime: "2019-01-15 00:00:00", }, { description: "every 5 weekday in the future", at: &AtTime{ WeekDay: "2,5", Hour: "", Minute: "", }, baseTime: "2019-01-09 23:33:01", expectTime: "2019-01-11 00:00:00", }, { description: "every 5 weekday in the future tz", at: &AtTime{ WeekDay: "2,5", Hour: "", Minute: "", TZ: "America/Los_Angeles", }, baseTime: "2019-01-09 23:33:01", expectTime: "2019-01-11 00:00:00", }, } for _, useCase := range useCases { err := useCase.at.Init() assert.Nil(t, err) var loc *time.Location if useCase.at.TZ != "" { loc, _ = time.LoadLocation(useCase.at.TZ) } var baseTime time.Time if loc != nil { baseTime, err = time.ParseInLocation(timeLayout, useCase.baseTime, loc) assert.Nil(t, err, useCase.description) } else { baseTime, err = time.Parse(timeLayout, useCase.baseTime) assert.Nil(t, err, useCase.description) } var expectTime time.Time if loc != nil { expectTime, err = time.ParseInLocation(timeLayout, useCase.expectTime, loc) assert.Nil(t, err, useCase.description) } else { expectTime, err = time.Parse(timeLayout, useCase.expectTime) assert.Nil(t, err, useCase.description) } actualTime := useCase.at.Next(baseTime) assert.Equal(t, expectTime, actualTime, useCase.description) //without tz baseTime, err = time.Parse(timeLayout, useCase.baseTime) actualTime = useCase.at.Next(baseTime) assert.Equal(t, expectTime, actualTime, useCase.description) } } func TestNewDuration(t *testing.T) { var useCases = []struct { description string value int unit string expected time.Duration hasError bool }{ { description: "sec test", value: 3, unit: DurationSecond, expected: 3 * time.Second, }, { description: "min test", value: 4, unit: DurationMinute, expected: 4 * time.Minute, }, { description: "hour test", value: 5, unit: DurationHour, expected: 5 * time.Hour, }, { description: "day test", value: 12, unit: DurationDay, expected: 12 * time.Hour * 24, }, { description: "week test", value: 7, unit: DurationWeek, expected: 7 * time.Hour * 24 * 7, }, { description: "error test", value: 4, unit: "abc", hasError: true, }, } for _, useCase := range useCases { actual, err := NewDuration(useCase.value, useCase.unit) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } else if err != nil { assert.Nil(t, err, useCase.description) continue } assert.Equal(t, useCase.expected, actual, useCase.description) } } func TestIdMatcher_Match(t *testing.T) { { ts, err := TimeAt("1 sec ahead") assert.Nil(t, err) assert.EqualValues(t, ts.Unix()-1, time.Now().Unix()) } { //invalid duration unit _, err := TimeAt("1 d ahead") assert.NotNil(t, err) } } func TestTimeDiff(t *testing.T) { var useCases = []struct { description string base time.Time expression string exectedDiff time.Duration hasError bool }{ { description: "now test", expression: "now", base: time.Now(), exectedDiff: 0, }, { description: "tomorrow test", expression: "tomorrow", base: time.Now(), exectedDiff: time.Hour * 24, }, { description: "yesterday test", expression: "yesterday", base: time.Now(), exectedDiff: -time.Hour * 24, }, { description: "empty expr error", hasError: true, }, { description: "parsing expr error", expression: "a232", hasError: true, }, { description: "2 days ago test", expression: "2daysago", base: time.Now(), exectedDiff: -time.Hour * 48, }, { description: "2 days in the future", expression: "2day in the future", base: time.Now(), exectedDiff: time.Hour * 48, }, { description: "days in the future", expression: "day InTheFuture", base: time.Now(), exectedDiff: time.Hour * 24, }, { description: "2 hours before", expression: "2hourbefore", base: time.Now(), exectedDiff: -time.Hour * 2, }, { description: "2 hours later", expression: "2 hoursLater", base: time.Now(), exectedDiff: time.Hour * 2, }, { description: "timezone", expression: "nowInUTC", base: time.Now(), exectedDiff: 0, }, { description: "invalid timezone error", expression: "nowInBAAA", base: time.Now(), hasError: true, }, { description: "day in UTC", expression: "2 days ago in UTC", base: time.Now(), exectedDiff: -time.Hour * 48, }, { description: "day in UTC", expression: "weekAheadInUTC", base: time.Now(), exectedDiff: time.Hour * 24 * 7, }, } for _, useCase := range useCases { actual, err := TimeDiff(useCase.base, useCase.expression) if useCase.hasError { assert.NotNil(t, err, useCase.description) continue } else if err != nil { assert.Nil(t, err, useCase.description) continue } expected := useCase.base.Add(useCase.exectedDiff) assert.EqualValues(t, expected.Unix(), actual.Unix(), useCase.description) } } func TestDayElapsedInPct(t *testing.T) { t0, _ := time.Parse(DateFormatToLayout("yyyy-MM-dd hh:mm:ss"), "2017-01-01 12:00:00") elapsedPct := ElapsedDay(t0) assert.EqualValues(t, 50, math.Round(100*elapsedPct)) elapsed, err := ElapsedToday("") assert.Nil(t, err) assert.True(t, elapsed > 0) remaining, err := RemainingToday("") assert.Nil(t, err) assert.True(t, remaining > 0) assert.EqualValues(t, int(remaining+elapsed), 1) } func TestTimeWindow_Range(t *testing.T) { var useCaes = []struct { description string window *TimeWindow expectedCount int }{ { description: "empty window", window: &TimeWindow{}, expectedCount: 1, }, { description: "loopback window", window: &TimeWindow{ TimeFormat: "yyyy-MM-dd HH:mm:ss", Loopback: &Duration{Value: 3, Unit: "sec"}, EndDate: "2011-12-01 15:01:01", Interval: &Duration{Value: 1, Unit: "sec"}, }, expectedCount: 4, }, { description: "default loopback with interval window", window: &TimeWindow{ Loopback: &Duration{Value: 3, Unit: "min"}, Interval: &Duration{Value: 1, Unit: "min"}, }, expectedCount: 4, }, { description: "default loopback window", window: &TimeWindow{ Loopback: &Duration{Value: 3, Unit: "min"}, }, expectedCount: 2, }, { description: "date range window", window: &TimeWindow{ TimeFormat: "yyyy-MM-dd HH:mm:ss", StartDate: "2011-12-01 15:01:01", EndDate: "2011-12-01 15:02:01", Interval: &Duration{Value: 10, Unit: "sec"}}, expectedCount: 7, }, } for _, useCase := range useCaes { count := 0 err := useCase.window.Range(func(time time.Time) (bool, error) { count++ return true, nil }) assert.Nil(t, err, useCase.description) assert.Equal(t, useCase.expectedCount, count, useCase.description) } } func TestN(t *testing.T) { }toolbox-0.33.2/tokenizer.go000066400000000000000000000353601374110251100156300ustar00rootroot00000000000000package toolbox import ( "fmt" "strings" "unicode" ) //Matcher represents a matcher, that matches input from offset position, it returns number of characters matched. type Matcher interface { //Match matches input starting from offset, it return number of characters matched Match(input string, offset int) (matched int) } //Token a matchable input type Token struct { Token int Matched string } //Tokenizer represents a token scanner. type Tokenizer struct { matchers map[int]Matcher Input string Index int InvalidToken int EndOfFileToken int } //Nexts matches the first of the candidates func (t *Tokenizer) Nexts(candidates ...int) *Token { for _, candidate := range candidates { result := t.Next(candidate) if result.Token != t.InvalidToken { return result } } return &Token{t.InvalidToken, ""} } //Next tries to match a candidate, it returns token if imatching is successful. func (t *Tokenizer) Next(candidate int) *Token { offset := t.Index if !(offset < len(t.Input)) { return &Token{t.EndOfFileToken, ""} } if candidate == t.EndOfFileToken { return &Token{t.InvalidToken, ""} } if matcher, ok := t.matchers[candidate]; ok { matchedSize := matcher.Match(t.Input, offset) if matchedSize > 0 { t.Index = t.Index + matchedSize return &Token{candidate, t.Input[offset : offset+matchedSize]} } } else { panic(fmt.Sprintf("failed to lookup matcher for %v", candidate)) } return &Token{t.InvalidToken, ""} } //NewTokenizer creates a new NewTokenizer, it takes input, invalidToken, endOfFileToeken, and matchers. func NewTokenizer(input string, invalidToken int, endOfFileToken int, matcher map[int]Matcher) *Tokenizer { return &Tokenizer{ matchers: matcher, Input: input, Index: 0, InvalidToken: invalidToken, EndOfFileToken: endOfFileToken, } } //CharactersMatcher represents a matcher, that matches any of Chars. type CharactersMatcher struct { Chars string //characters to be matched } //Match matches any characters defined in Chars in the input, returns 1 if character has been matched func (m CharactersMatcher) Match(input string, offset int) int { var matched = 0 if offset >= len(input) { return matched } outer: for _, r := range input[offset:] { for _, candidate := range m.Chars { if candidate == r { matched++ continue outer } } break } return matched } //NewCharactersMatcher creates a new character matcher func NewCharactersMatcher(chars string) Matcher { return &CharactersMatcher{Chars: chars} } //EOFMatcher represents end of input matcher type EOFMatcher struct { } //Match returns 1 if end of input has been reached otherwise 0 func (m EOFMatcher) Match(input string, offset int) int { if offset+1 == len(input) { return 1 } return 0 } //IntMatcher represents a matcher that finds any int in the input type IntMatcher struct{} //Match matches a literal in the input, it returns number of character matched. func (m IntMatcher) Match(input string, offset int) int { var matched = 0 if offset >= len(input) { return matched } for _, r := range input[offset:] { if !unicode.IsDigit(r) { break } matched++ } return matched } //NewIntMatcher returns a new integer matcher func NewIntMatcher() Matcher { return &IntMatcher{} } var dotRune = rune('.') var underscoreRune = rune('_') //LiteralMatcher represents a matcher that finds any literals in the input type LiteralMatcher struct{} //Match matches a literal in the input, it returns number of character matched. func (m LiteralMatcher) Match(input string, offset int) int { var matched = 0 if offset >= len(input) { return matched } for i, r := range input[offset:] { if i == 0 { if !unicode.IsLetter(r) { break } } else if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == dotRune || r == underscoreRune) { break } matched++ } return matched } //LiteralMatcher represents a matcher that finds any literals in the input type IdMatcher struct{} //Match matches a literal in the input, it returns number of character matched. func (m IdMatcher) Match(input string, offset int) int { var matched = 0 if offset >= len(input) { return matched } for i, r := range input[offset:] { if i == 0 { if !(unicode.IsLetter(r) || unicode.IsDigit(r)) { break } } else if !(unicode.IsLetter(r) || unicode.IsDigit(r) || r == dotRune || r == underscoreRune) { break } matched++ } return matched } //SequenceMatcher represents a matcher that finds any sequence until find provided terminators type SequenceMatcher struct { Terminators []string CaseSensitive bool matchAllIfNoTerminator bool runeTerminators []rune } func (m *SequenceMatcher) hasTerminator(candidate string) bool { var candidateLength = len(candidate) for _, terminator := range m.Terminators { terminatorLength := len(terminator) if len(terminator) > candidateLength { continue } if !m.CaseSensitive { if strings.ToLower(terminator) == strings.ToLower(string(candidate[:terminatorLength])) { return true } } if terminator == string(candidate[:terminatorLength]) { return true } } return false } //Match matches a literal in the input, it returns number of character matched. func (m *SequenceMatcher) Match(input string, offset int) int { var matched = 0 hasTerminator := false if offset >= len(input) { return matched } if len(m.runeTerminators) > 0 { return m.matchSingleTerminator(input, offset) } var i = 0 for ; i < len(input)-offset; i++ { if m.hasTerminator(string(input[offset+i:])) { hasTerminator = true break } } if !hasTerminator && !m.matchAllIfNoTerminator { return 0 } return i } func (m *SequenceMatcher) matchSingleTerminator(input string, offset int) int { matched := 0 hasTerminator := false outer: for i, r := range input[offset:] { for _, terminator := range m.runeTerminators { terminator = unicode.ToLower(terminator) if m.CaseSensitive { r = unicode.ToLower(r) terminator = unicode.ToLower(terminator) } if r == terminator { hasTerminator = true matched = i break outer } } } if !hasTerminator && !m.matchAllIfNoTerminator { return 0 } return matched } //NewSequenceMatcher creates a new matcher that finds all sequence until find at least one of the provided terminators func NewSequenceMatcher(terminators ...string) Matcher { result := &SequenceMatcher{ matchAllIfNoTerminator: true, Terminators: terminators, runeTerminators: []rune{}, } for _, terminator := range terminators { if len(terminator) != 1 { result.runeTerminators = []rune{} break } result.runeTerminators = append(result.runeTerminators, rune(terminator[0])) } return result } //NewTerminatorMatcher creates a new matcher that finds any sequence until find at least one of the provided terminators func NewTerminatorMatcher(terminators ...string) Matcher { result := &SequenceMatcher{ Terminators: terminators, runeTerminators: []rune{}, } for _, terminator := range terminators { if len(terminator) != 1 { result.runeTerminators = []rune{} break } result.runeTerminators = append(result.runeTerminators, rune(terminator[0])) } return result } //remainingSequenceMatcher represents a matcher that matches all reamining input type remainingSequenceMatcher struct{} //Match matches a literal in the input, it returns number of character matched. func (m *remainingSequenceMatcher) Match(input string, offset int) (matched int) { return len(input) - offset } //Creates a matcher that matches all remaining input func NewRemainingSequenceMatcher() Matcher { return &remainingSequenceMatcher{} } //CustomIdMatcher represents a matcher that finds any literals with additional custom set of characters in the input type customIdMatcher struct { Allowed map[rune]bool } func (m *customIdMatcher) isValid(r rune) bool { if unicode.IsLetter(r) || unicode.IsDigit(r) { return true } return m.Allowed[r] } //Match matches a literal in the input, it returns number of character matched. func (m *customIdMatcher) Match(input string, offset int) int { var matched = 0 if offset >= len(input) { return matched } for _, r := range input[offset:] { if !m.isValid(r) { break } matched++ } return matched } //NewCustomIdMatcher creates new custom matcher func NewCustomIdMatcher(allowedChars ...string) Matcher { var result = &customIdMatcher{ Allowed: make(map[rune]bool), } if len(allowedChars) == 1 && len(allowedChars[0]) > 0 { for _, allowed := range allowedChars[0] { result.Allowed[rune(allowed)] = true } } for _, allowed := range allowedChars { result.Allowed[rune(allowed[0])] = true } return result } //LiteralMatcher represents a matcher that finds any literals in the input type BodyMatcher struct { Begin string End string } //Match matches a literal in the input, it returns number of character matched. func (m *BodyMatcher) Match(input string, offset int) (matched int) { beginLen := len(m.Begin) endLen := len(m.End) uniEnclosed := m.Begin == m.End if offset+beginLen >= len(input) { return 0 } if input[offset:offset+beginLen] != m.Begin { return 0 } var depth = 1 var i = 1 for ; i < len(input)-offset; i++ { canCheckEnd := offset+i+endLen <= len(input) if !canCheckEnd { return 0 } if !uniEnclosed { canCheckBegin := offset+i+beginLen <= len(input) if canCheckBegin { if string(input[offset+i:offset+i+beginLen]) == m.Begin { depth++ } } } if string(input[offset+i:offset+i+endLen]) == m.End { depth-- } if depth == 0 { i += endLen break } } return i } //NewBodyMatcher creates a new body matcher func NewBodyMatcher(begin, end string) Matcher { return &BodyMatcher{Begin: begin, End: end} } // Parses SQL Begin End blocks func NewBlockMatcher(caseSensitive bool, sequenceStart string, sequenceTerminator string, nestedSequences []string, ignoredTerminators []string) Matcher { return &BlockMatcher{ CaseSensitive: caseSensitive, SequenceStart: sequenceStart, SequenceTerminator: sequenceTerminator, NestedSequences: nestedSequences, IgnoredTerminators: ignoredTerminators, } } type BlockMatcher struct { CaseSensitive bool SequenceStart string SequenceTerminator string NestedSequences []string IgnoredTerminators []string } func (m *BlockMatcher) Match(input string, offset int) (matched int) { sequenceStart := m.SequenceStart terminator := m.SequenceTerminator nestedSequences := m.NestedSequences ignoredTerminators := m.IgnoredTerminators in := input starterLen := len(sequenceStart) terminatorLen := len(terminator) if !m.CaseSensitive { sequenceStart = strings.ToLower(sequenceStart) terminator = strings.ToLower(terminator) for i, seq := range nestedSequences { nestedSequences[i] = strings.ToLower(seq) } for i, term := range ignoredTerminators { ignoredTerminators[i] = strings.ToLower(term) } in = strings.ToLower(input) } if offset+starterLen >= len(in) { return 0 } if in[offset:offset+starterLen] != sequenceStart { return 0 } var depth = 1 var i = 1 for ; i < len(in)-offset; i++ { canCheckEnd := offset+i+terminatorLen <= len(in) if !canCheckEnd { return 0 } canCheckBegin := offset+i+starterLen <= len(in) if canCheckBegin { beginning := in[offset+i : offset+i+starterLen] if beginning == sequenceStart { depth++ } else { for _, nestedSeq := range nestedSequences { nestedLen := len(nestedSeq) if offset+i+nestedLen >= len(in) { continue } beginning := in[offset+i : offset+i+nestedLen] if beginning == nestedSeq { depth++ break } } } } ignored := false for _, ignoredTerm := range ignoredTerminators { termLen := len(ignoredTerm) if offset+i+termLen >= len(in) { continue } ending := in[offset+i : offset+i+termLen] if ending == ignoredTerm { ignored = true break } } if !ignored && in[offset+i:offset+i+terminatorLen] == terminator && unicode.IsSpace(rune(in[offset+i-1])) { depth-- } if depth == 0 { i += terminatorLen break } } return i } //KeywordMatcher represents a keyword matcher type KeywordMatcher struct { Keyword string CaseSensitive bool } //Match matches keyword in the input, it returns number of character matched. func (m KeywordMatcher) Match(input string, offset int) (matched int) { if !(offset+len(m.Keyword)-1 < len(input)) { return 0 } if m.CaseSensitive { if input[offset:offset+len(m.Keyword)] == m.Keyword { return len(m.Keyword) } } else { if strings.ToLower(input[offset:offset+len(m.Keyword)]) == strings.ToLower(m.Keyword) { return len(m.Keyword) } } return 0 } //KeywordsMatcher represents a matcher that finds any of specified keywords in the input type KeywordsMatcher struct { Keywords []string CaseSensitive bool } //Match matches any specified keyword, it returns number of character matched. func (m KeywordsMatcher) Match(input string, offset int) (matched int) { for _, keyword := range m.Keywords { if len(input)-offset < len(keyword) { continue } if m.CaseSensitive { if input[offset:offset+len(keyword)] == keyword { return len(keyword) } } else { if strings.ToLower(input[offset:offset+len(keyword)]) == strings.ToLower(keyword) { return len(keyword) } } } return 0 } //NewKeywordsMatcher returns a matcher for supplied keywords func NewKeywordsMatcher(caseSensitive bool, keywords ...string) Matcher { return &KeywordsMatcher{CaseSensitive: caseSensitive, Keywords: keywords} } //IllegalTokenError represents illegal token error type IllegalTokenError struct { Illegal *Token Message string Expected []int Position int } func (e *IllegalTokenError) Error() string { return fmt.Sprintf("%v; illegal token at %v [%v], expected %v, but had: %v", e.Message, e.Position, e.Illegal.Matched, e.Expected, e.Illegal.Token) } //NewIllegalTokenError create a new illegal token error func NewIllegalTokenError(message string, expected []int, position int, found *Token) error { return &IllegalTokenError{ Message: message, Illegal: found, Expected: expected, Position: position, } } //ExpectTokenOptionallyFollowedBy returns second matched token or error if first and second group was not matched func ExpectTokenOptionallyFollowedBy(tokenizer *Tokenizer, first int, errorMessage string, second ...int) (*Token, error) { _, _ = ExpectToken(tokenizer, "", first) return ExpectToken(tokenizer, errorMessage, second...) } //ExpectToken returns the matched token or error func ExpectToken(tokenizer *Tokenizer, errorMessage string, candidates ...int) (*Token, error) { token := tokenizer.Nexts(candidates...) hasMatch := HasSliceAnyElements(candidates, token.Token) if !hasMatch { return nil, NewIllegalTokenError(errorMessage, candidates, tokenizer.Index, token) } return token, nil } toolbox-0.33.2/tokenizer_test.go000066400000000000000000000103001374110251100166520ustar00rootroot00000000000000package toolbox_test import ( "testing" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestNewTokenizer(t *testing.T) { tokenizer := toolbox.NewTokenizer("Z Abcf", 0, -1, map[int]toolbox.Matcher{ 101: toolbox.KeywordMatcher{"Abc", true}, 201: toolbox.CharactersMatcher{Chars: " \n\t"}, 102: toolbox.LiteralMatcher{}, }, ) assert.Equal(t, 102, tokenizer.Nexts(101, 201, 102).Token) assert.Equal(t, 201, tokenizer.Nexts(101, 201, 102).Token) assert.Equal(t, 101, tokenizer.Nexts(101, 201, 102).Token) assert.Equal(t, 102, tokenizer.Nexts(101, 201, 102).Token) } func Test_NewCustomIdMatcher(t *testing.T) { { matcher := toolbox.NewCustomIdMatcher("$") assert.Equal(t, 5, matcher.Match("Z $Abcf", 2)) assert.Equal(t, 1, matcher.Match("Z Abcf", 0)) assert.Equal(t, 0, matcher.Match("### ##", 0)) } matcher := toolbox.NewCustomIdMatcher("_", "(", ")") assert.Equal(t, 1, matcher.Match(" v_sc()", 6)) } func Test_NewSequenceMatcher(t *testing.T) { matcher := toolbox.NewSequenceMatcher("&&", "||") assert.Equal(t, 2, matcher.Match("123", 1)) assert.Equal(t, 4, matcher.Match("123 && 123", 0)) } func Test_NewSingleSequenceMatcher(t *testing.T) { matcher := toolbox.NewSequenceMatcher("&") assert.Equal(t, 0, matcher.Match("123", 1)) assert.Equal(t, 5, matcher.Match("12345&3", 0)) } func TestMatchKeyword(t *testing.T) { matcher := toolbox.KeywordMatcher{"Abc", true} assert.Equal(t, 3, matcher.Match("Z Abcf", 2)) assert.Equal(t, 0, matcher.Match("Z Abcf", 0)) assert.Equal(t, 3, matcher.Match("Z Abc", 2)) } func TestMatchWhitespace(t *testing.T) { matcher := toolbox.CharactersMatcher{Chars: " \n\t"} assert.Equal(t, 0, matcher.Match("1, 2, 3", 0)) assert.Equal(t, 2, matcher.Match("1, \t2, 3", 2)) } func TestLiteralMatcher(t *testing.T) { matcher := toolbox.LiteralMatcher{} assert.Equal(t, 0, matcher.Match(" abc ", 0)) assert.Equal(t, 4, matcher.Match(" a1bc", 1)) } func TestEOFMatcher(t *testing.T) { matcher := toolbox.EOFMatcher{} assert.Equal(t, 0, matcher.Match(" abc ", 0)) assert.Equal(t, 1, matcher.Match(" a1bc", 4)) } func TestKeywordsMatcher(t *testing.T) { { matcher := toolbox.KeywordsMatcher{Keywords: []string{"ab", "xy"}, CaseSensitive: false} assert.Equal(t, 2, matcher.Match(" abcde", 1)) assert.Equal(t, 0, matcher.Match(" abcde", 0)) } { matcher := toolbox.KeywordsMatcher{Keywords: []string{"AB", "xy"}, CaseSensitive: true} assert.Equal(t, 2, matcher.Match(" ABcde", 1)) assert.Equal(t, 0, matcher.Match("abcde", 0)) } } func TestBodyMatcher(t *testing.T) { { matcher := toolbox.BodyMatcher{Begin: "{", End: "}"} var text = " { { \n} } " pos := matcher.Match(text, 1) assert.Equal(t, 16, pos) } { matcher := toolbox.BodyMatcher{Begin: "begin", End: "end"} var text = " begin { \n} end " pos := matcher.Match(text, 1) assert.Equal(t, 20, pos) } } func TestBlockMatcher(t *testing.T) { { matcher := toolbox.NewBlockMatcher(false, "begin", "end;", []string{"CASE"}, []string{"END IF"}) text := ` TRIGGER users_before_insert BEFORE INSERT ON users FOR EACH ROW BEGIN SELECT users_seq.NEXTVAL INTO :new.id FROM dual; END; INSERT INTO DUMMY(ID, NAME) VALUES(2, 'xyz'); ` matcher.Match(text, 65) } { matcher := toolbox.BlockMatcher{ CaseSensitive: false, SequenceStart: "begin", SequenceTerminator: "end", NestedSequences: []string{"case"}, IgnoredTerminators: []string{"end if"}, } text := "\n\n" + "BEgin\n" + "IF get_version()=20\n" + "select *\n" + "from table\n" + "where color = case inventory when 1 then 'brown' when 2 then 'red' END;\n" + "END IF\n" + "END;" pos := matcher.Match(text, 2) assert.Equal(t, 128, pos) } { matcher := toolbox.BlockMatcher{ CaseSensitive: false, SequenceStart: "begin", SequenceTerminator: "end;", NestedSequences: []string{"case"}, } text := "bEgIn case 123deabc then 22 End; End;" pos := matcher.Match(text, 0) assert.Equal(t, 37, pos) matcher.CaseSensitive = true pos = matcher.Match(text, 0) assert.Equal(t, 0, pos) matcher.SequenceTerminator = "End;" matcher.SequenceStart = "bEgIn" pos = matcher.Match(text, 0) assert.Equal(t, 37, pos) } } toolbox-0.33.2/types.go000066400000000000000000000143151374110251100147570ustar00rootroot00000000000000package toolbox import ( "fmt" "reflect" "time" ) //Zeroable represents object that can call IsZero type Zeroable interface { //IsZero returns true, if value of object was zeroed. IsZero() bool } //IsInt returns true if input is an int func IsInt(input interface{}) bool { switch input.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return true } return false } //IsNumber returns true if type is either float or int func IsNumber(input interface{}) bool { return IsFloat(input) || IsInt(input) } //IsFloat returns true if input is a float func IsFloat(input interface{}) bool { switch input.(type) { case float32, float64: return true } return false } //IsBool returns true if input is a boolean func IsBool(input interface{}) bool { switch input.(type) { case bool: return true } return false } //IsString returns true if input is a string func IsString(input interface{}) bool { switch input.(type) { case string: return true } return false } //CanConvertToString checks if input can be converted to string func CanConvertToString(input interface{}) bool { return reflect.TypeOf(input).AssignableTo(reflect.TypeOf("")) } //IsTime returns true if input is a time func IsTime(input interface{}) bool { switch input.(type) { case time.Time: return true case *time.Time: return true } return false } //IsMap returns true if input is a map func IsMap(input interface{}) bool { switch input.(type) { case map[string]interface{}: return true } candidateType := DereferenceType(reflect.TypeOf(input)) return candidateType.Kind() == reflect.Map } //IsStruct returns true if input is a map func IsStruct(input interface{}) bool { if input == nil { return false } inputType := DereferenceType(input) return inputType.Kind() == reflect.Struct } //IsSlice returns true if input is a map func IsSlice(input interface{}) bool { switch input.(type) { case []interface{}: return true case []string: return true } candidateType := DereferenceType(reflect.TypeOf(input)) return candidateType.Kind() == reflect.Slice } //IsFunc returns true if input is a funct func IsFunc(input interface{}) bool { candidateType := DereferenceType(reflect.TypeOf(input)) return candidateType.Kind() == reflect.Func } //IsZero returns true if input is a zeroable func IsZero(input interface{}) bool { if zeroable, ok := input.(Zeroable); ok { return zeroable.IsZero() } return false } //IsPointer returns true if input is a pointer func IsPointer(input interface{}) bool { if reflectType, ok := input.(reflect.Type); ok { return reflectType.Kind() == reflect.Ptr } return reflect.TypeOf(input).Kind() == reflect.Ptr } //AssertPointerKind checks if input is a pointer of the passed in kind, if not it panic with message including name func AssertPointerKind(input interface{}, kind reflect.Kind, name string) { AssertTypeKind(reflect.TypeOf(input), reflect.Ptr, name) AssertTypeKind(reflect.TypeOf(input).Elem(), kind, name) } //AssertKind checks if input is of the passed in kind, if not it panic with message including name func AssertKind(input interface{}, kind reflect.Kind, name string) { AssertTypeKind(reflect.TypeOf(input), kind, name) } //AssertTypeKind checks if dataType is of the passed in kind, if not it panic with message including name func AssertTypeKind(dataType reflect.Type, kind reflect.Kind, name string) { if dataType.Kind() != kind { panic(fmt.Sprintf("failed to check: %v - expected kind: %v but found %v (%v)", name, kind.String(), dataType.Kind(), dataType.String())) } } //DiscoverValueByKind returns unwrapped input that matches expected kind, or panic if this is not possible func DiscoverValueByKind(input interface{}, expected reflect.Kind) reflect.Value { result, err := TryDiscoverValueByKind(input, expected) if err == nil { return result } panic(err) } //TryDiscoverValueByKind returns unwrapped input that matches expected kind, or panic if this is not possible func TryDiscoverValueByKind(input interface{}, expected reflect.Kind) (reflect.Value, error) { value, ok := input.(reflect.Value) if !ok { value = reflect.ValueOf(input) } if value.Kind() == expected { return value, nil } else if value.Kind() == reflect.Ptr { return TryDiscoverValueByKind(value.Elem(), expected) } else if value.Kind() == reflect.Interface { return TryDiscoverValueByKind(value.Elem(), expected) } return value, fmt.Errorf("failed to discover value by kind expected: %v, actual:%T on %v:", expected.String(), value.Type(), value) } //IsValueOfKind returns true if passed in input is of supplied kind. func IsValueOfKind(input interface{}, kind reflect.Kind) bool { value, ok := input.(reflect.Value) if !ok { value = reflect.ValueOf(input) } if value.Kind() == kind { return true } else if value.Kind() == reflect.Ptr { return IsValueOfKind(value.Elem(), kind) } else if value.Kind() == reflect.Interface { return IsValueOfKind(value.Elem(), kind) } return false } //DiscoverTypeByKind returns unwrapped input type that matches expected kind, or panic if this is not possible func DiscoverTypeByKind(input interface{}, expected reflect.Kind) reflect.Type { result, err := TryDiscoverTypeByKind(input, expected) if err != nil { panic(err) } return result } //TryDiscoverTypeByKind returns unwrapped input type that matches expected kind, or error func TryDiscoverTypeByKind(input interface{}, expected reflect.Kind) (reflect.Type, error) { value, ok := input.(reflect.Type) if !ok { value = reflect.TypeOf(input) } if value.Kind() == expected { return value, nil } else if value.Kind() == reflect.Ptr || value.Kind() == reflect.Slice { return TryDiscoverTypeByKind(value.Elem(), expected) } return nil, fmt.Errorf("failed to discover type by kind %v, on %v:", expected.String(), value) } //DiscoverComponentType returns type unwrapped from pointer, slice or map func DiscoverComponentType(input interface{}) reflect.Type { valueType, ok := input.(reflect.Type) if !ok { valueType = reflect.TypeOf(input) } if valueType.Kind() == reflect.Ptr { return DiscoverComponentType(valueType.Elem()) } else if valueType.Kind() == reflect.Slice { return valueType.Elem() } else if valueType.Kind() == reflect.Map { return valueType.Elem() } return valueType } toolbox-0.33.2/types_test.go000066400000000000000000000053031374110251100160130ustar00rootroot00000000000000package toolbox_test import ( "fmt" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) type User2 struct { Name string `column:"name"` DateOfBirth time.Time `column:"date" dateLayout:"2006-01-02 15:04:05.000000"` Id int `autoincrement:"true"` Other string `transient:"true"` } func TestAssertKind(t *testing.T) { toolbox.AssertKind(User2{}, reflect.Struct, "user") toolbox.AssertKind((*User2)(nil), reflect.Ptr, "user") defer func() { if err := recover(); err != nil { expected := "failed to check: User - expected kind: ptr but found struct (toolbox_test.User2)" actual := fmt.Sprintf("%v", err) assert.Equal(t, actual, expected, "Assert Kind") } }() toolbox.AssertKind(User2{}, reflect.Ptr, "User") } func TestAssertPointerKind(test *testing.T) { toolbox.AssertPointerKind(&User2{}, reflect.Struct, "user") toolbox.AssertPointerKind((*User2)(nil), reflect.Struct, "user") } func TestTypeDetection(t *testing.T) { assert.False(t, toolbox.IsFloat(3)) assert.True(t, toolbox.IsFloat(3.0)) assert.True(t, toolbox.CanConvertToFloat(3.0)) assert.True(t, toolbox.CanConvertToFloat("3")) assert.True(t, toolbox.CanConvertToFloat(3)) assert.False(t, toolbox.CanConvertToFloat(false)) assert.False(t, toolbox.IsInt(3.0)) assert.True(t, toolbox.IsInt(3)) assert.True(t, toolbox.CanConvertToInt(3)) assert.True(t, toolbox.CanConvertToInt("3")) assert.False(t, toolbox.CanConvertToInt(true)) assert.False(t, toolbox.CanConvertToInt(3.3)) assert.False(t, toolbox.IsBool(3.0)) assert.True(t, toolbox.IsBool(true)) assert.False(t, toolbox.IsString(3.0)) assert.True(t, toolbox.IsString("abc")) assert.True(t, toolbox.CanConvertToString("abc")) assert.False(t, toolbox.CanConvertToString(3.2)) assert.False(t, toolbox.IsTime(3.0)) assert.True(t, toolbox.IsTime(time.Now())) var timeValues = make([]time.Time, 1) assert.True(t, toolbox.IsZero(timeValues[0])) assert.False(t, toolbox.IsZero(time.Now())) assert.False(t, toolbox.IsZero("")) aString := "" assert.True(t, toolbox.IsPointer(&aString)) assert.False(t, toolbox.IsPointer(reflect.TypeOf(aString))) assert.True(t, toolbox.IsPointer(&aString)) } func TestIsValueOfKind(t *testing.T) { text := "" assert.True(t, toolbox.IsValueOfKind(&text, reflect.Ptr)) assert.False(t, toolbox.IsValueOfKind(&text, reflect.Struct)) assert.True(t, toolbox.IsValueOfKind(&text, reflect.String)) values := make([]interface{}, 1) values[0] = 1 assert.True(t, toolbox.IsValueOfKind(&values[0], reflect.Int)) } func TestIsFunc(t *testing.T) { var f = func() {} assert.True(t, toolbox.IsFunc(&f)) assert.True(t, toolbox.IsFunc(f)) assert.False(t, toolbox.IsFunc("")) } toolbox-0.33.2/uri.go000066400000000000000000000074231374110251100144140ustar00rootroot00000000000000package toolbox import ( "fmt" "net/url" "os" "path/filepath" "strings" ) //ExtractURIParameters parses URIs to extract {} defined in templateURI from requestURI, it returns extracted parameters and flag if requestURI matched templateURI func ExtractURIParameters(templateURI, requestURI string) (map[string]string, bool) { var expectingValue, expectingName bool var name, value string var uriParameters = make(map[string]string) maxLength := len(templateURI) + len(requestURI) var requestURIIndex, templateURIIndex int questionMarkPosition := strings.Index(requestURI, "?") if questionMarkPosition != -1 { requestURI = string(requestURI[:questionMarkPosition]) } for k := 0; k < maxLength; k++ { var requestChar, routingChar string if requestURIIndex < len(requestURI) { requestChar = requestURI[requestURIIndex : requestURIIndex+1] } if templateURIIndex < len(templateURI) { routingChar = templateURI[templateURIIndex : templateURIIndex+1] } if (!expectingValue && !expectingName) && requestChar == routingChar && routingChar != "" { requestURIIndex++ templateURIIndex++ continue } if routingChar == "}" { expectingName = false templateURIIndex++ } if expectingValue && requestChar == "/" { expectingValue = false } if expectingName && templateURIIndex < len(templateURI) { name += routingChar templateURIIndex++ } if routingChar == "{" { expectingValue = true expectingName = true templateURIIndex++ } if expectingValue && requestURIIndex < len(requestURI) { value += requestChar requestURIIndex++ } if !expectingValue && !expectingName && len(name) > 0 { uriParameters[name] = value name = "" value = "" } } if len(name) > 0 && len(value) > 0 { uriParameters[name] = value } matched := requestURIIndex == len(requestURI) && templateURIIndex == len(templateURI) return uriParameters, matched } //URLStripPath removes path from URL func URLStripPath(URL string) string { protoIndex := strings.Index(URL, "://") if protoIndex != -1 { pathIndex := strings.Index(string(URL[protoIndex+3:]), "/") if pathIndex != -1 { return string(URL[:protoIndex+3+pathIndex]) } } return URL } //URLPathJoin joins URL paths func URLPathJoin(baseURL, path string) string { if path == "" { return baseURL } if strings.HasPrefix(path, "/") { return URLStripPath(baseURL) + path } if !strings.HasSuffix(baseURL, "/") { baseURL += "/" } return baseURL + path } //URLBase returns base URL func URLBase(URL string) string { parsedURL, err := url.Parse(URL) if err != nil || parsedURL.Path == "" { return URL } pathPosition := strings.Index(URL, parsedURL.Path) if pathPosition == -1 { return URL } return string(URL[:pathPosition]) } //URLSplit returns URL with parent path and resource name func URLSplit(URL string) (string, string) { parsedURL, err := url.Parse(URL) if err != nil || parsedURL.Path == "" { return URL, "" } splitPosition := strings.LastIndex(parsedURL.Path, "/") if splitPosition == -1 { return URL, "" } return fmt.Sprintf("%v%v", URLBase(URL), string(parsedURL.Path[:splitPosition])), string(parsedURL.Path[splitPosition+1:]) } //Filename reformat file name func Filename(filename string) string { if strings.Contains(filename, ":/") { if parsed, err := url.Parse(filename); err == nil { filename = parsed.Path } } var root = make([]string, 0) if strings.HasPrefix(filename, "/") { root = append(root, "/") } elements := append(root, strings.Split(filename, "/")...) filename = filepath.Join(elements...) return filename } //OpenFile open file converting path to elements and rebuling path safety with path.Join func OpenFile(filename string) (*os.File, error) { var file = Filename(filename) var result, err = os.Open(file) return result, err } toolbox-0.33.2/uri_helper.go000066400000000000000000000022631374110251100157500ustar00rootroot00000000000000package toolbox import ( "net/url" "path" ) //FileSchema file:// var FileSchema = "file://" //Deprecated start using url.Resource //ExtractMimeType extracts mime type by extension func ExtractMimeType(file string) string { extension := path.Ext(file) if len(extension) > 1 { extension = extension[1:] } if mimeType, ok := FileExtensionMimeType[extension]; ok { return mimeType } return "text/plain" } //QueryIntValue returns query value for passed in url's name or default value func QueryIntValue(u *url.URL, name string, defaultValue int) int { value := u.Query().Get(name) if value == "" { return defaultValue } result := AsInt(value) if result != 0 { return result } return defaultValue } //QueryBoolValue returns query value for passed in url's name or default value func QueryBoolValue(u *url.URL, name string, defaultValue bool) bool { value := u.Query().Get(name) if value == "" { return defaultValue } return AsBoolean(value) } //QueryValue returns query value for passed in url's name or default value func QueryValue(u *url.URL, name, defaultValue string) string { value := u.Query().Get(name) if value == "" { return defaultValue } return value } toolbox-0.33.2/uri_helper_test.go000066400000000000000000000011501374110251100170010ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "net/url" "testing" ) func Test_QueryValue(t *testing.T) { u, err := url.Parse("http://localhost/?k1=v1&k2=2&k3=false") assert.Nil(t, err) assert.Equal(t, "v1", toolbox.QueryValue(u, "k1", "default")) assert.Equal(t, "default", toolbox.QueryValue(u, "k10", "default")) assert.Equal(t, 2, toolbox.QueryIntValue(u, "k2", 3)) assert.Equal(t, 3, toolbox.QueryIntValue(u, "k10", 3)) assert.Equal(t, false, toolbox.QueryBoolValue(u, "k3", true)) assert.Equal(t, true, toolbox.QueryBoolValue(u, "k10", true)) } toolbox-0.33.2/uri_test.go000066400000000000000000000064431374110251100154540ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/url" "testing" ) func TestExtractURIParameters(t *testing.T) { { parameters, matched := toolbox.ExtractURIParameters("/v1/path/{app}/{version}/", "/v1/path/app/1.0/?v=12") assert.True(t, matched) if !matched { t.FailNow() } assert.Equal(t, 2, len(parameters)) assert.Equal(t, "app", parameters["app"]) assert.Equal(t, "1.0", parameters["version"]) } { parameters, matched := toolbox.ExtractURIParameters("/v1/path/{ids}/{sub}/a/{name}", "/v1/path/1,2,3,4,5/subpath/a/abc") assert.True(t, matched) assert.Equal(t, 3, len(parameters)) assert.Equal(t, "1,2,3,4,5", parameters["ids"]) assert.Equal(t, "subpath", parameters["sub"]) assert.Equal(t, "abc", parameters["name"]) } { parameters, matched := toolbox.ExtractURIParameters("/v1/path/{ids}", "/v1/path/this-is-test") assert.True(t, matched) assert.Equal(t, 1, len(parameters)) assert.Equal(t, "this-is-test", parameters["ids"]) } { _, matched := toolbox.ExtractURIParameters("/v2/path/{ids}/{sub}/a/{name}", "/v1/path/1,2,3,4,5/subpath/a/abc") assert.False(t, matched) } { _, matched := toolbox.ExtractURIParameters("/v1/path1/{ids}/{sub}/a/{name}", "/v1/path/1,2,3,4,5/subpath/a/abc") assert.False(t, matched) } { _, matched := toolbox.ExtractURIParameters("/v1/path1/{ids}/{sub}/a/{name}", "/v1/path/1,2,3,4,5/subpath/a/abc") assert.False(t, matched) } { _, matched := toolbox.ExtractURIParameters("/v1/path/{ids}", "/v1/path/1") assert.True(t, matched) } { _, matched := toolbox.ExtractURIParameters("/v1/reverse/", "/v1/reverse/") assert.True(t, matched) } { parameters, matched := toolbox.ExtractURIParameters("/v1/path/{ids}/{sub}/a/{name}", "/v1/path/1,2,3,4,5/subpath/a/abcwrwr") assert.True(t, matched) assert.Equal(t, 3, len(parameters)) assert.Equal(t, "1,2,3,4,5", parameters["ids"]) assert.Equal(t, "subpath", parameters["sub"]) assert.Equal(t, "abcwrwr", parameters["name"]) } } func TestURLBase(t *testing.T) { URL := "http://github.com/abc" baseURL := toolbox.URLBase(URL) assert.Equal(t, "http://github.com", baseURL) } func TestURLSplit(t *testing.T) { { URL := "http://github.com/abc/trter/rds" parentURL, resource := toolbox.URLSplit(URL) assert.Equal(t, "http://github.com/abc/trter", parentURL) assert.Equal(t, "rds", resource) } } func TestURLStripPath(t *testing.T) { { URL := "http://github.com/abc" assert.EqualValues(t, "http://github.com", toolbox.URLStripPath(URL)) } { URL := "http://github.com" assert.EqualValues(t, "http://github.com", toolbox.URLStripPath(URL)) } } func TestURL_Rename(t *testing.T) { { URL := "http://github.com/abc/" resource := url.NewResource(URL) resource.Rename("/tmp/abc") assert.Equal(t, "http://github.com//tmp/abc", resource.URL) } } func TestURLPathJoin(t *testing.T) { { URL := "http://github.com/abc" assert.EqualValues(t, "http://github.com/abc/path/a.txt", toolbox.URLPathJoin(URL, "path/a.txt")) } { URL := "http://github.com/abc/" assert.EqualValues(t, "http://github.com/abc/path/a.txt", toolbox.URLPathJoin(URL, "path/a.txt")) } { URL := "http://github.com/abc/" assert.EqualValues(t, "http://github.com/a.txt", toolbox.URLPathJoin(URL, "/a.txt")) } } toolbox-0.33.2/url/000077500000000000000000000000001374110251100140625ustar00rootroot00000000000000toolbox-0.33.2/url/key.go000066400000000000000000000002721374110251100152020ustar00rootroot00000000000000package url //AES256Key represents custom key type AES256Key struct { Key []byte Base64Key string Base64KeyMd5Hash string Base64KeySha256Hash string } toolbox-0.33.2/url/resource.go000066400000000000000000000261361374110251100162500ustar00rootroot00000000000000package url import ( "bytes" "encoding/base64" "fmt" "github.com/viant/toolbox" "github.com/viant/toolbox/storage" "gopkg.in/yaml.v2" "io/ioutil" "net/url" "os" "path" "strings" "time" ) //Resource represents a URL based resource, with enriched meta info type Resource struct { URL string `description:"resource URL or relative or absolute path" required:"true"` //URL of resource Credentials string `description:"credentials file"` //name of credential file or credential key depending on implementation ParsedURL *url.URL `json:"-"` //parsed URL resource Cache string `description:"local cache path"` //Cache path for the resource, if specified resource will be cached in the specified path CustomKey *AES256Key `description:" content encryption key"` CacheExpiryMs int //CacheExpiryMs expiry time in ms modificationTag int64 init string } //Clone creates a clone of the resource func (r *Resource) Clone() *Resource { return &Resource{ init: r.init, URL: r.URL, Credentials: r.Credentials, ParsedURL: r.ParsedURL, Cache: r.Cache, CacheExpiryMs: r.CacheExpiryMs, } } var defaultSchemePorts = map[string]int{ "ssh": 22, "scp": 22, "http": 80, "https": 443, } //Host returns url's host name with user name if user name is part of url func (r *Resource) Host() string { result := r.ParsedURL.Hostname() + ":" + r.Port() if r.ParsedURL.User != nil { result = r.ParsedURL.User.Username() + "@" + result } return result } //CredentialURL returns url's with provided credential func (r *Resource) CredentialURL(username, password string) string { var urlCredential = "" if username != "" { urlCredential = username if password != "" { urlCredential += ":" + password } urlCredential += "@" } result := r.ParsedURL.Scheme + "://" + urlCredential + r.ParsedURL.Hostname() + ":" + r.Port() + r.ParsedURL.Path if r.ParsedURL.RawQuery != "" { result += "?" + r.ParsedURL.RawQuery } return result } //Path returns url's path directory, assumption is that directory does not have extension, if path ends with '/' it is being stripped. func (r *Resource) DirectoryPath() string { if r.ParsedURL == nil { return "" } var result = r.ParsedURL.Path parent, name := path.Split(result) if path.Ext(name) != "" { result = parent } if strings.HasSuffix(result, "/") { result = string(result[:len(result)-1]) } return result } //Port returns url's port func (r *Resource) Port() string { port := r.ParsedURL.Port() if port == "" && r.ParsedURL != nil { if value, ok := defaultSchemePorts[r.ParsedURL.Scheme]; ok { port = toolbox.AsString(value) } } return port } //Download downloads data from URL, it returns data as []byte, or error, if resource is cacheable it first look into cache func (r *Resource) Download() ([]byte, error) { if r == nil { return nil, fmt.Errorf("Fail to download content on empty resource") } if r.Cachable() { content := r.readFromCache() if content != nil { return content, nil } } service, err := storage.NewServiceForURL(r.URL, r.Credentials) if err != nil { return nil, err } object, err := service.StorageObject(r.URL) if err != nil { return nil, err } reader, err := service.Download(object) if err != nil { return nil, err } defer reader.Close() content, err := ioutil.ReadAll(reader) if err != nil { return nil, err } if r.Cachable() { _ = ioutil.WriteFile(r.Cache, content, 0666) } return content, err } //DownloadText returns a text downloaded from url func (r *Resource) DownloadText() (string, error) { var result, err = r.Download() if err != nil { return "", err } return string(result), err } //Decode decodes url's data into target, it support JSON and YAML exp. func (r *Resource) Decode(target interface{}) (err error) { defer func() { if err != nil { err = fmt.Errorf("failed to decode: %v, %v", r.URL, err) } }() if r.ParsedURL == nil { if r.ParsedURL, err = url.Parse(r.URL); err != nil { return err } } ext := path.Ext(r.ParsedURL.Path) switch ext { case ".yaml", ".yml": err = r.YAMLDecode(target) default: err = r.JSONDecode(target) } return err } //DecoderFactory returns new decoder factory for resource func (r *Resource) DecoderFactory() toolbox.DecoderFactory { ext := path.Ext(r.ParsedURL.Path) switch ext { case ".yaml", ".yml": return toolbox.NewYamlDecoderFactory() default: return toolbox.NewJSONDecoderFactory() } } //Decode decodes url's data into target, it takes decoderFactory which decodes data into target func (r *Resource) DecodeWith(target interface{}, decoderFactory toolbox.DecoderFactory) error { if r == nil { return fmt.Errorf("fail to %T decode on empty resource", decoderFactory) } if decoderFactory == nil { return fmt.Errorf("fail to decode %v, decoderFactory was empty", r.URL) } var content, err = r.Download() if err != nil { return err } text := string(content) if toolbox.IsNewLineDelimitedJSON(text) { if aSlice, err := toolbox.NewLineDelimitedJSON(text); err == nil { return toolbox.DefaultConverter.AssignConverted(target, aSlice) } } err = decoderFactory.Create(bytes.NewReader(content)).Decode(target) if err != nil { return fmt.Errorf("failed to decode: %v, payload: %s", err, content) } return err } //Rename renames URI name of this resource func (r *Resource) Rename(name string) (err error) { var _, currentName = toolbox.URLSplit(r.URL) if currentName == "" && strings.HasSuffix(r.URL, "/") { _, currentName = toolbox.URLSplit(r.URL[:len(r.URL)-1]) currentName += "/" } r.URL = strings.Replace(r.URL, currentName, name, 1) r.ParsedURL, err = url.Parse(r.URL) return err } //JSONDecode decodes json resource into target func (r *Resource) JSONDecode(target interface{}) error { return r.DecodeWith(target, toolbox.NewJSONDecoderFactory()) } //JSONDecode decodes yaml resource into target func (r *Resource) YAMLDecode(target interface{}) error { if interfacePrt, ok := target.(*interface{}); ok { var data interface{} if err := r.DecodeWith(&data, toolbox.NewYamlDecoderFactory()); err != nil { return err } if toolbox.IsSlice(data) { *interfacePrt = data return nil } } var mapSlice = yaml.MapSlice{} if err := r.DecodeWith(&mapSlice, toolbox.NewYamlDecoderFactory()); err != nil { return err } if !toolbox.IsMap(target) { return toolbox.DefaultConverter.AssignConverted(target, mapSlice) } resultMap := toolbox.AsMap(target) for _, v := range mapSlice { resultMap[toolbox.AsString(v.Key)] = v.Value } return nil } func (r *Resource) readFromCache() []byte { if toolbox.FileExists(r.Cache) { info, err := os.Stat(r.Cache) var isExpired = false if err == nil && r.CacheExpiryMs > 0 { elapsed := time.Now().Sub(info.ModTime()) isExpired = elapsed > time.Second*time.Duration(r.CacheExpiryMs) } content, err := ioutil.ReadFile(r.Cache) if err == nil && !isExpired { return content } } return nil } //Cachable returns true if resource is cachable func (r *Resource) Cachable() bool { return r.Cache != "" } func computeResourceModificationTag(resource *Resource) (int64, error) { service, err := storage.NewServiceForURL(resource.URL, resource.Credentials) if err != nil { return 0, err } object, err := service.StorageObject(resource.URL) if err != nil { return 0, err } var fileInfo = object.FileInfo() if object.IsContent() { return fileInfo.Size() + fileInfo.ModTime().UnixNano(), nil } var result int64 = 0 objects, err := service.List(resource.URL) if err != nil { return 0, err } for _, object := range objects { objectResource := NewResource(object.URL()) if objectResource.ParsedURL.Path == resource.ParsedURL.Path { continue } modificationTag, err := computeResourceModificationTag(NewResource(object.URL(), resource.Credentials)) if err != nil { return 0, err } result += modificationTag } return result, nil } func (r *Resource) HasChanged() (changed bool, err error) { if r.modificationTag == 0 { r.modificationTag, err = computeResourceModificationTag(r) return false, err } var recentModificationTag int64 recentModificationTag, err = computeResourceModificationTag(r) if err != nil { return false, err } if recentModificationTag != r.modificationTag { changed = true r.modificationTag = recentModificationTag } return changed, err } func normalizeURL(URL string) string { if strings.Contains(URL, "://") { var protoPosition = strings.Index(URL, "://") if protoPosition != -1 { var urlSuffix = string(URL[protoPosition+3:]) urlSuffix = strings.Replace(urlSuffix, "//", "/", len(urlSuffix)) URL = string(URL[:protoPosition+3]) + urlSuffix } return URL } if !strings.HasPrefix(URL, "/") { currentDirectory, _ := os.Getwd() if strings.Contains(URL, "..") { fragments := strings.Split(URL, "/") var index = 0 var offset = 0 if fragments[0] == "." { offset = 1 } for index = offset; index < len(fragments); index++ { var fragment = fragments[index] if fragment == ".." { currentDirectory, _ = path.Split(currentDirectory) if strings.HasSuffix(currentDirectory, "/") { currentDirectory = string(currentDirectory[:len(currentDirectory)-1]) } continue } break } return toolbox.FileSchema + path.Join(currentDirectory, strings.Join(fragments[index:], "/")) } currentDirectory, err := os.Getwd() if err == nil { candidate := path.Join(currentDirectory, URL) URL = candidate } } return toolbox.FileSchema + URL } func (r *Resource) Init() (err error) { if r.init == r.URL { return nil } r.init = r.URL r.URL = normalizeURL(r.URL) r.ParsedURL, err = url.Parse(r.URL) return err } //DownloadBase64 loads base64 resource content func (r *Resource) DownloadBase64() (string, error) { storageService, err := storage.NewServiceForURL(r.URL, r.Credentials) if err != nil { return "", err } reader, err := storage.Download(storageService, r.URL) if err != nil { return "", err } defer func() { _ = reader.Close() }() data, err := ioutil.ReadAll(reader) if err != nil { return "", err } _, err = base64.StdEncoding.DecodeString(string(data)) if err == nil { return string(data), nil } return base64.StdEncoding.EncodeToString(data), nil } //NewResource returns a new resource for provided URL, followed by optional credential, cache and cache expiryMs. func NewResource(Params ...interface{}) *Resource { if len(Params) == 0 { return nil } var URL = toolbox.AsString(Params[0]) URL = normalizeURL(URL) var credential string if len(Params) > 1 { credential = toolbox.AsString(Params[1]) } var cache string if len(Params) > 2 { cache = toolbox.AsString(Params[2]) } var cacheExpiryMs int if len(Params) > 3 { cacheExpiryMs = toolbox.AsInt(Params[3]) } parsedURL, _ := url.Parse(URL) return &Resource{ init: URL, ParsedURL: parsedURL, URL: URL, Credentials: credential, Cache: cache, CacheExpiryMs: cacheExpiryMs, } } toolbox-0.33.2/url/resource_test.go000066400000000000000000000115651374110251100173070ustar00rootroot00000000000000package url_test import ( "fmt" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "github.com/viant/toolbox/url" "io/ioutil" "os" "path" "strings" "testing" ) func TestNewResource(t *testing.T) { { var resource = url.NewResource("./../../test") fmt.Printf("%v\n", resource.ParsedURL.Path) assert.True(t, strings.HasSuffix(resource.DirectoryPath(), "viant/test")) } { var resource = url.NewResource("https://raw.githubusercontent.com/viant/toolbox/master/LICENSE.txt") assert.EqualValues(t, resource.ParsedURL.String(), "https://raw.githubusercontent.com/viant/toolbox/master/LICENSE.txt") //data, err := resource.Download() //assert.Nil(t, err) //assert.NotNil(t, data) } { var resource = url.NewResource("https://raw.githubusercontent.com/viant//toolbox//master/LICENSE.txt") assert.Equal(t, "https://raw.githubusercontent.com/viant/toolbox/master/LICENSE.txt", resource.URL) } { var resource = url.NewResource("./../test") assert.True(t, strings.HasSuffix(resource.DirectoryPath(), "/toolbox/test")) } { var resource = url.NewResource("../test") assert.True(t, strings.HasSuffix(resource.DirectoryPath(), "/toolbox/test")) } } func TestNew_CredentialURL(t *testing.T) { { var resource = url.NewResource("https://raw.githubusercontent.com:80/viant/toolbox/master/LICENSE.txt?check=1&p=2") var URL = resource.CredentialURL("smith", "123") assert.EqualValues(t, "https://smith:123@raw.githubusercontent.com:80/viant/toolbox/master/LICENSE.txt?check=1&p=2", URL) } { var resource = url.NewResource("https://raw.githubusercontent.com:80/viant/toolbox/master/LICENSE.txt") var URL = resource.CredentialURL("smith", "") assert.EqualValues(t, "https://smith@raw.githubusercontent.com:80/viant/toolbox/master/LICENSE.txt", URL) } } func TestNew_DirectoryPath(t *testing.T) { { var resource = url.NewResource("https://raw.githubusercontent.com:80/viant/toolbox/master/LICENSE.txt") assert.EqualValues(t, "/viant/toolbox/master", resource.DirectoryPath()) } { var resource = url.NewResource("https://raw.githubusercontent.com:80/viant/toolbox/master/avc") assert.EqualValues(t, "/viant/toolbox/master/avc", resource.DirectoryPath()) } { var resource = url.NewResource("hter") assert.True(t, strings.HasSuffix(resource.DirectoryPath(), "hter")) } } func TestResource_YamlDecode(t *testing.T) { if os.Getenv("TMPDIR")== "" { return } var filename1 = path.Join(os.Getenv("TMPDIR"), "resource1.yaml") var filename2 = path.Join(os.Getenv("TMPDIR"), "resource2.yaml") _ = toolbox.RemoveFileIfExist(filename1, filename2) var aMap = map[string]interface{}{ "a": 1, "b": "123", "c": []int{1, 3, 6}, } file, err := os.OpenFile(filename1, os.O_CREATE|os.O_RDWR, 0644) if assert.Nil(t, err) { err = toolbox.NewYamlEncoderFactory().Create(file).Encode(aMap) assert.Nil(t, err) } { var resource = url.NewResource(filename1) assert.EqualValues(t, resource.ParsedURL.String(), toolbox.FileSchema+filename1) var resourceData = make(map[string]interface{}) err = resource.YAMLDecode(&resourceData) assert.Nil(t, err) assert.EqualValues(t, resourceData["a"], 1) assert.EqualValues(t, resourceData["b"], "123") } YAML := `init: defaultUser: &defaultUser name: bob age: 18 pipeline: test: init: users: <<: *defaultUser age: 24 action: print message: I got $users` err = ioutil.WriteFile(filename2, []byte(YAML), 0644) assert.Nil(t, err) { var resource = url.NewResource(filename2) var resourceData = make(map[string]interface{}) err = resource.YAMLDecode(&resourceData) assert.Nil(t, err) if normalized, err := toolbox.NormalizeKVPairs(resourceData); err == nil { resourceData = toolbox.AsMap(normalized) } //TODO add actual test once yaml reference is patched //exposes issue with yaml reference } } func TestResource_JsonDecode(t *testing.T) { if os.Getenv("TMPDIR") == "" { return } var filename = path.Join(os.Getenv("TMPDIR"), "resource.json") toolbox.RemoveFileIfExist(filename) defer toolbox.RemoveFileIfExist(filename) var aMap = map[string]interface{}{ "a": 1, "b": "123", } file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644) if assert.Nil(t, err) { err = toolbox.NewJSONEncoderFactory().Create(file).Encode(aMap) assert.Nil(t, err) } var resource = url.NewResource(filename) assert.EqualValues(t, resource.ParsedURL.String(), toolbox.FileSchema+filename) var resourceData = make(map[string]interface{}) err = resource.Decode(&resourceData) assert.Nil(t, err) assert.EqualValues(t, resourceData["a"], 1) assert.EqualValues(t, resourceData["b"], "123") } func TestResource_DecoderFactory(t *testing.T) { { resource := url.NewResource("abc.yaml") factory := resource.DecoderFactory() assert.NotNil(t, factory) } { resource := url.NewResource("abc.json") factory := resource.DecoderFactory() assert.NotNil(t, factory) } } toolbox-0.33.2/value_provider.go000066400000000000000000000247211374110251100166430ustar00rootroot00000000000000package toolbox import ( "bytes" "fmt" "github.com/pkg/errors" "io/ioutil" "os" "strings" "time" ) //ValueProvider represents a value provider type ValueProvider interface { //Get returns a value for passed in context and arguments. Context can be used to manage state. Get(context Context, arguments ...interface{}) (interface{}, error) } //ValueProviderRegistry registry of value providers type ValueProviderRegistry interface { Register(name string, valueProvider ValueProvider) Contains(name string) bool Names() []string Get(name string) ValueProvider } type valueProviderRegistryImpl struct { registry map[string](ValueProvider) } func (r valueProviderRegistryImpl) Register(name string, valueProvider ValueProvider) { r.registry[name] = valueProvider } func (r valueProviderRegistryImpl) Contains(name string) bool { _, ok := r.registry[name] return ok } func (r valueProviderRegistryImpl) Get(name string) ValueProvider { if result, ok := r.registry[name]; ok { return result } panic(fmt.Sprintf("failed to lookup name: %v", name)) } func (r valueProviderRegistryImpl) Names() []string { return MapKeysToStringSlice(&r.registry) } //NewValueProviderRegistry create new NewValueProviderRegistry func NewValueProviderRegistry() ValueProviderRegistry { var result ValueProviderRegistry = &valueProviderRegistryImpl{ registry: make(map[string]ValueProvider), } return result } type envValueProvider struct{} func (p envValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { key := arguments[0].(string) value, found := os.LookupEnv(key) if found { return value, nil } return nil, fmt.Errorf("failed to lookup %v in env", key) } //NewEnvValueProvider returns a provider that returns a value of env variables. func NewEnvValueProvider() ValueProvider { var result ValueProvider = &envValueProvider{} return result } type dateOfBirthProvider struct{} func (p dateOfBirthProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { if len(arguments) < 1 { return nil, errors.New("expected | [month], [day], [timeformat]") } now := time.Now() age := AsInt(arguments[0]) var month int = int(now.Month()) var day int = now.Day() var timeFormat = "yyyy-MM-dd" if len(arguments) >= 2 { month = AsInt(arguments[1]) } if len(arguments) >= 3 { day = AsInt(arguments[2]) } if len(arguments) >= 4 { timeFormat = AsString(arguments[3]) } dateOfBirthText := fmt.Sprintf("%04d-%02d-%02d", now.Year()-age, month, day) date, err := time.Parse(DateFormatToLayout("yyyy-MM-dd"), dateOfBirthText) if err != nil { return nil, err } return date.Format(DateFormatToLayout(timeFormat)), nil } //NewDateOfBirthValueProvider provider for computing date for supplied expected age, month and day func NewDateOfBirthrovider() ValueProvider { return &dateOfBirthProvider{} } type castedValueProvider struct{} func (p castedValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { key := arguments[0].(string) if len(arguments) < 2 { return nil, fmt.Errorf("failed to cast to %v due to invalid number of arguments, Wanted 2 but had:%v", key, len(arguments)) } switch key { case "time": if len(arguments) != 3 { return nil, fmt.Errorf("failed to cast to time due to invalid number of arguments expected 2, but had %v", len(arguments)-1) } castedTime, err := ParseTime(AsString(arguments[1]), AsString(arguments[2])) if err != nil { return nil, fmt.Errorf("failed to cast to time %v due to %v", AsString(arguments[1]), err) } return castedTime, nil case "int": return AsInt(arguments[1]), nil case "int32": return int32(AsInt(arguments[1])), nil case "int64": return int64(AsInt(arguments[1])), nil case "float32": return float32(AsFloat(arguments[1])), nil case "float": return AsFloat(arguments[1]), nil case "bool": return AsBoolean(arguments[1]), nil case "string": return AsString(arguments[1]), nil } return nil, fmt.Errorf("failed to cast to %v - unsupported type", key) } //NewCastedValueProvider return a provider that return casted value type func NewCastedValueProvider() ValueProvider { var result ValueProvider = &castedValueProvider{} return result } type currentTimeProvider struct{} func (p currentTimeProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { return time.Now(), nil } //NewCurrentTimeProvider returns a provder that returns time.Now() func NewCurrentTimeProvider() ValueProvider { var result ValueProvider = ¤tTimeProvider{} return result } type timeDiffProvider struct{} func (p timeDiffProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { var resultTime time.Time var durationDelta time.Duration var err error if len(arguments) >= 1 { var timeValue *time.Time var timeLiteral = AsString(arguments[0]) if timeValue, err = TimeAt(timeLiteral); err != nil { if timeValue, err = ToTime(arguments[0], ""); err != nil { return nil, err } } resultTime = *timeValue } if len(arguments) >= 3 { var val = AsInt(arguments[1]) var timeUnit = strings.ToLower(AsString(arguments[2])) durationDelta, err = NewDuration(val, timeUnit) if err != nil { return nil, err } } var format = "" if len(arguments) == 4 { format = AsString(arguments[3]) } resultTime = resultTime.Add(durationDelta) switch format { case "unix": return int(resultTime.Unix()+resultTime.UnixNano()) / 1000000000, nil case "timestamp": return int(resultTime.Unix()+resultTime.UnixNano()) / 1000000, nil default: if len(format) > 0 { return resultTime.Format(DateFormatToLayout(format)), nil } } return resultTime, nil } //NewTimeDiffProvider returns a provider that delta, time unit and optionally format //format as java date format or unix or timestamp func NewTimeDiffProvider() ValueProvider { var result ValueProvider = &timeDiffProvider{} return result } type weekdayProvider struct{} func (p weekdayProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { var now = time.Now() return int(now.Weekday()), nil } func NewWeekdayProvider() ValueProvider { return &weekdayProvider{} } type nilValueProvider struct{} func (p nilValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { return nil, nil } //NewNilValueProvider returns a provider that returns a nil func NewNilValueProvider() ValueProvider { var result ValueProvider = &nilValueProvider{} return result } //ConstValueProvider represnet a const value provider type ConstValueProvider struct { Value string } //Get return provider value func (p ConstValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { return p.Value, nil } //NewConstValueProvider returns a provider that returns a nil func NewConstValueProvider(value string) ValueProvider { var result ValueProvider = &ConstValueProvider{Value: value} return result } type currentDateProvider struct{} func (p currentDateProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { return time.Now().Local().Format("20060102"), nil } //NewCurrentDateProvider returns a provider that returns current date in the format yyyymmdd, i.e. 20170205 func NewCurrentDateProvider() ValueProvider { var result ValueProvider = ¤tDateProvider{} return result } //Dictionary represents simply lookup interface type Dictionary interface { //Get returns value for passed in key or error Get(key string) (interface{}, error) //Exists checks if key exists Exists(key string) bool } //MapDictionary alias to map of string and interface{} type MapDictionary map[string]interface{} func (d *MapDictionary) Get(name string) (interface{}, error) { if result, found := (*d)[name]; found { return result, nil } return nil, fmt.Errorf("failed to lookup: %v", name) } func (d *MapDictionary) Exists(name string) bool { _, found := (*d)[name] return found } type dictionaryProvider struct { dictionaryContentKey interface{} } func (p dictionaryProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { if len(arguments) == 0 { return nil, fmt.Errorf("expected at least one argument but had 0") } var key = AsString(arguments[0]) var dictionary Dictionary context.GetInto(p.dictionaryContentKey, &dictionary) if len(arguments) == 1 && !dictionary.Exists(key) { return nil, nil } return dictionary.Get(key) } //NewDictionaryProvider creates a new Dictionary provider, it takes a key context that is a MapDictionary pointer func NewDictionaryProvider(contextKey interface{}) ValueProvider { return &dictionaryProvider{contextKey} } type betweenPredicateValueProvider struct{} func (p *betweenPredicateValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { if len(arguments) != 2 { return nil, fmt.Errorf("expected 2 arguments with between predicate but had %v", len(arguments)) } predicate := NewBetweenPredicate(arguments[0], arguments[1]) return &predicate, nil } //NewBetweenPredicateValueProvider returns a new between value provider func NewBetweenPredicateValueProvider() ValueProvider { return &betweenPredicateValueProvider{} } type withinSecPredicateValueProvider struct{} func (p *withinSecPredicateValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { if len(arguments) != 3 { return nil, fmt.Errorf("expected 3 arguments predicate, but had %v", len(arguments)) } if arguments[0] == "now" { arguments[0] = time.Now() } dateFormat := AsString(arguments[2]) dateLayout := DateFormatToLayout(dateFormat) targetTime := AsTime(arguments[0], dateLayout) if targetTime == nil { return nil, fmt.Errorf("Unable convert %v to time.Time", arguments[0]) } delta := AsInt(arguments[1]) predicate := NewWithinPredicate(*targetTime, delta, dateLayout) return &predicate, nil } //NewWithinSecPredicateValueProvider returns a new within second value provider func NewWithinSecPredicateValueProvider() ValueProvider { return &withinSecPredicateValueProvider{} } type fileValueProvider struct { trim bool } func (p *fileValueProvider) Get(context Context, arguments ...interface{}) (interface{}, error) { filePath := AsString(arguments[0]) fileContent, err := ioutil.ReadFile(filePath) if err != nil { panic(err) } if p.trim { fileContent = bytes.TrimSpace(fileContent) } result := string(fileContent) return result, nil } //NewFileValueProvider create new file value provider func NewFileValueProvider(trim bool) ValueProvider { return &fileValueProvider{trim: trim} } toolbox-0.33.2/value_provider_test.go000066400000000000000000000133411374110251100176760ustar00rootroot00000000000000package toolbox_test import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/viant/toolbox" ) func TestNewEnvValueProvider(t *testing.T) { provider := toolbox.NewEnvValueProvider() { _, err := provider.Get(nil, "USER") assert.Nil(t, err) } { _, err := provider.Get(nil, "_blahblah") assert.NotNil(t, err) } } func TestNewCastedValueProvider(t *testing.T) { provider := toolbox.NewCastedValueProvider() for _, source := range []interface{}{2, "2"} { value, err := provider.Get(nil, "int", source) assert.Nil(t, err) assert.Equal(t, 2, value) } { _, err := provider.Get(nil, "int") assert.NotNil(t, err, "Invalid number of parameters") } for _, source := range []interface{}{2, "2", 2.0} { value, err := provider.Get(nil, "float", source) assert.Nil(t, err) assert.Equal(t, 2.0, value) } for _, source := range []interface{}{true, "true", 1} { value, err := provider.Get(nil, "bool", source) assert.Nil(t, err) assert.Equal(t, true, value) } for _, source := range []interface{}{1, "1"} { value, err := provider.Get(nil, "string", source) assert.Nil(t, err) assert.Equal(t, "1", value) } { value, err := provider.Get(nil, "time", "2016-02-22 12:32:01 UTC", toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z")) assert.Nil(t, err) timeValue := value.(time.Time) assert.Equal(t, int64(1456144321), timeValue.Unix()) } { _, err := provider.Get(nil, "time", "2016/02-22 12:32:01 UTC", toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss")) assert.NotNil(t, err, "invalid format") } { _, err := provider.Get(nil, "time", "2016-02-22 12:32:01 UTC", toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z"), "1") assert.NotNil(t, err, "to many parameters") } { _, err := provider.Get(nil, "ABC", "1") assert.NotNil(t, err, "NOT IMPLEMENTED") } } func TestNewWeekdayProvider(t *testing.T) { provider := toolbox.NewWeekdayProvider() value, err := provider.Get(toolbox.NewContext(), nil) assert.Nil(t, err) assert.NotNil(t, value) assert.Equal(t, toolbox.AsInt(value), int(time.Now().Weekday())) } func TestNewCurrentTimeProvider(t *testing.T) { provider := toolbox.NewCurrentTimeProvider() value, err := provider.Get(nil) assert.Nil(t, err) assert.NotNil(t, value) } func TestNewCurrentDateProvider(t *testing.T) { provider := toolbox.NewCurrentDateProvider() value, err := provider.Get(nil) assert.Nil(t, err) assert.NotNil(t, value) } func TestNewNilProvider(t *testing.T) { provider := toolbox.NewNilValueProvider() value, err := provider.Get(nil) assert.Nil(t, err) assert.Nil(t, value) } func TestNewValueProviderRegistry(t *testing.T) { registry := toolbox.NewValueProviderRegistry() assert.False(t, registry.Contains("a")) registry.Register("a", toolbox.NewNilValueProvider()) assert.True(t, registry.Contains("a")) provider := registry.Get("a") assert.NotNil(t, provider) assert.Equal(t, 1, len(registry.Names())) } func TestNewDictionaryProviderRegistry(t *testing.T) { var dictionary toolbox.MapDictionary = make(map[string]interface{}) var key toolbox.MapDictionary dictionary["k1"] = "123" dictionary["k2"] = "xyz" provider := toolbox.NewDictionaryProvider(&key) context := toolbox.NewContext() context.Put(&key, &dictionary) { value, err := provider.Get(context, "k1") assert.Nil(t, err) assert.Equal(t, "123", value) } { value, err := provider.Get(context, "k2") assert.Nil(t, err) assert.Equal(t, "xyz", value) } { value, err := provider.Get(context, "k13", "true") assert.NotNil(t, err) assert.Nil(t, value) } } func Test_NewNewTimeProvider(t *testing.T) { var now = time.Now() provider := toolbox.NewTimeDiffProvider() { result, err := provider.Get(nil, "now", 1, "day") assert.Nil(t, err) var timeResult = toolbox.AsTime(result, "") in23Hours := now.Add(23 * time.Hour) in25Hours := now.Add(25 * time.Hour) assert.True(t, timeResult.After(in23Hours)) assert.True(t, timeResult.Before(in25Hours)) } { result, err := provider.Get(nil, "now", 1, "hour", "timestamp") assert.Nil(t, err) var timeResult = toolbox.AsInt(result) in59Mins := int(now.Add(59*time.Minute).Unix() * 1000) in61Mins := int(now.Add(61*time.Minute).Unix() * 1000) assert.True(t, in59Mins < timeResult) assert.True(t, timeResult < in61Mins) } { result, err := provider.Get(nil, "now", 1, "week", "unix") assert.Nil(t, err) var timeResult = toolbox.AsInt(result) in6Days := int(now.Add(6 * 24 * time.Hour).Unix()) in8Days := int(now.Add(8 * 24 * time.Hour).Unix()) assert.True(t, in6Days < timeResult) assert.True(t, timeResult < in8Days) } { result, err := provider.Get(nil, "now", 1, "hour", "h") assert.Nil(t, err) assert.Equal(t, time.Now().Hour()%12+1, toolbox.AsInt(result)) } } func Test_NewDateOfBirthValueProvider(t *testing.T) { //provider := toolbox.NewDateOfBirthrovider() //{ // result, err := provider.Get(toolbox.NewContext(), 3, 6, 3) // assert.Nil(t, err) // assert.EqualValues(t, "2016-06-03", toolbox.AsString(result)) //} // //{ // result, err := provider.Get(toolbox.NewContext(), 3, 6, 3, "yyyy-MM-dd") // assert.Nil(t, err) // assert.EqualValues(t, "2016-06-03", toolbox.AsString(result)) //} // //{ // result, err := provider.Get(toolbox.NewContext(), 3, 6, 3, "yyyy") // assert.Nil(t, err) // assert.EqualValues(t, "2016", toolbox.AsString(result)) //} // //{ // result, err := provider.Get(toolbox.NewContext(), 3, 9, 2, "yyyy-MM") // assert.Nil(t, err) // assert.EqualValues(t, "2016-09", toolbox.AsString(result)) //} // //{ // result, err := provider.Get(toolbox.NewContext(), 5, 12, 25, "-MM-dd") // assert.Nil(t, err) // assert.EqualValues(t, "-12-25", toolbox.AsString(result)) //} // //{ // _, err := provider.Get(toolbox.NewContext()) // assert.NotNil(t, err) // //} } toolbox-0.33.2/waitgroup_helper.go000066400000000000000000000015041374110251100171670ustar00rootroot00000000000000package toolbox import ( "sync" "sync/atomic" "time" ) // WaitGroup that waits with a timeout // Returns true if timeout exceeded and false if there was no timeout func WaitTimeout(wg *sync.WaitGroup, duration time.Duration) bool { done := make(chan bool, 1) closed := int32(0) defer func() { if atomic.CompareAndSwapInt32(&closed, 0, 1) { close(done) } }() go func() { wg.Wait() if atomic.LoadInt32(&closed) == 0 { done <- true } }() select { case <-done: //Wait till the task is complete and channel get unblocked return false //No durationToken. Normal execution of task completion case <-time.After(duration): //Wait till durationToken to elapse //TODO: time.After() creates a timer that does not get GC until timer durationToken gets elapsed. Need to use AfterFunc return true //Timed out } } toolbox-0.33.2/waitgroup_helper_test.go000066400000000000000000000013301374110251100202230ustar00rootroot00000000000000package toolbox import ( "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func TestWaitTimeout_TimeoutTriggered(t *testing.T) { var wg sync.WaitGroup go sleep(&wg, time.Second) time.Sleep(100 * time.Millisecond) isTimeOut := WaitTimeout(&wg, 100 * time.Millisecond) assert.Equal(t, true, isTimeOut) } func TestWaitTimeout_NoTimeout(t *testing.T) { var wg sync.WaitGroup go sleep(&wg, 100 * time.Millisecond) //task will sleep for 3 seconds but timeout is set only for 5 second isTimeOut := WaitTimeout(&wg, time.Second) assert.Equal(t, false, isTimeOut) } //Method that sleeps for 3 seconds func sleep(wg *sync.WaitGroup, duration time.Duration) { wg.Add(1) time.Sleep(duration) wg.Done() } toolbox-0.33.2/writer_at.go000066400000000000000000000021371374110251100156120ustar00rootroot00000000000000package toolbox import ( "sync" ) //ByteWriterAt represents a bytes writer at type ByteWriterAt struct { mutex *sync.Mutex Buffer []byte position int } //WriteAt returns number of written bytes or error func (w *ByteWriterAt) WriteAt(p []byte, offset int64) (n int, err error) { w.mutex.Lock() if int(offset) == w.position { w.Buffer = append(w.Buffer, p...) w.position += len(p) w.mutex.Unlock() return len(p), nil } else if w.position < int(offset) { var diff = (int(offset) - w.position) var fillingBytes = make([]byte, diff) w.position += len(fillingBytes) w.Buffer = append(w.Buffer, fillingBytes...) w.mutex.Unlock() return w.WriteAt(p, offset) } else { for i := 0; i < len(p); i++ { var index = int(offset) + i if index < len(w.Buffer) { w.Buffer[int(offset)+i] = p[i] } else { w.Buffer = append(w.Buffer, p[i:]...) break } } w.mutex.Unlock() return len(p), nil } } //NewWriterAt returns a new instance of byte writer at func NewByteWriterAt() *ByteWriterAt { return &ByteWriterAt{ mutex: &sync.Mutex{}, Buffer: make([]byte, 0), } } toolbox-0.33.2/writer_at_test.go000066400000000000000000000005301374110251100166440ustar00rootroot00000000000000package toolbox_test import ( "github.com/stretchr/testify/assert" "github.com/viant/toolbox" "testing" ) func TestWriterAt_WriteAt(t *testing.T) { writer := toolbox.NewByteWriterAt() writer.WriteAt([]byte{0x2}, 1) writer.WriteAt([]byte{0x1}, 0) writer.WriteAt([]byte{0x3}, 2) assert.Equal(t, []byte{0x1, 0x02, 0x3}, writer.Buffer) } toolbox-0.33.2/yaml.go000066400000000000000000000032021374110251100145460ustar00rootroot00000000000000package toolbox import ( "bytes" "fmt" "gopkg.in/yaml.v2" ) //AsYamlText converts data structure int text YAML func AsYamlText(source interface{}) (string, error) { if IsStruct(source) || IsMap(source) || IsSlice(source) { buf := new(bytes.Buffer) err := yaml.NewEncoder(buf).Encode(source) return buf.String(), err } return "", fmt.Errorf("unsupported type: %T", source) } //NormalizeKVPairs converts slice of KV paris into a map, and map[interface{}]interface{} to map[string]interface{} func NormalizeKVPairs(source interface{}) (interface{}, error) { if source == nil { return source, nil } isDataStruct := IsMap(source) || IsStruct(source) || IsSlice(source) var err error var normalized interface{} if isDataStruct { var aMap = make(map[string]interface{}) err = ProcessMap(source, func(k, value interface{}) bool { var key = AsString(k) aMap[key] = value if value == nil { return true } if IsMap(value) || IsSlice(value) || IsStruct(value) { if normalized, err = NormalizeKVPairs(value); err == nil { aMap[key] = normalized } } return true }) if err == nil { return aMap, nil } if IsSlice(aMap) { return source, err } if IsSlice(source) { //yaml style map conversion if applicable aSlice := AsSlice(source) if len(aSlice) == 0 { return source, nil } for i, item := range aSlice { if item == nil { continue } if IsMap(item) || IsSlice(item) { if normalized, err = NormalizeKVPairs(item); err == nil { aSlice[i] = normalized } else { return source, nil } } } return aSlice, nil } } return source, err } toolbox-0.33.2/yaml_test.go000066400000000000000000000031661374110251100156160ustar00rootroot00000000000000package toolbox import ( "encoding/json" "strings" "testing" "github.com/stretchr/testify/assert" yaml "gopkg.in/yaml.v2" ) func TestNormalizeKVPairs(t *testing.T) { { //yaml case YAML := `- Requests: - URL: http://localhost:5000 Method: GET Header: aHeader: - "v1" - "v2" someOtherHeader: - "CP=RTO" Body: "hey there" Cookies: - Name: aHeader Value: a-value DYAMLomain: "localhost" Expires: "2023-12-16T20:17:38Z" RawExpires: Sat, 16 Dec 2023 20:17:38 GMT` var data interface{} err := yaml.NewDecoder(strings.NewReader(YAML)).Decode(&data) assert.Nil(t, err) normalized, err := NormalizeKVPairs(data) assert.Nil(t, err) requests := AsMap(AsSlice(normalized)[0])["Requests"] request := AsMap(AsSlice(requests)[0]) assert.Equal(t, "http://localhost:5000", request["URL"]) header := AsMap(request["Header"]) assert.Equal(t, []interface{}{"v1", "v2"}, header["aHeader"]) } { JSON := `[ {"Key":"k1", "Value":"v1"}, {"Key":"k2", "Value":"v2"}, {"Key":"k3", "Value":[ {"Key":"k1", "Value":"v1", "Attr":2} ]}]` var data interface{} err := json.NewDecoder(strings.NewReader(JSON)).Decode(&data) assert.Nil(t, err) normalized, err := NormalizeKVPairs(data) assert.Nil(t, err) aMap := AsMap(normalized) assert.Equal(t, "v1", aMap["k1"]) assert.Equal(t, "v2", aMap["k2"]) aSlice := AsSlice(aMap["k3"]) assert.NotNil(t, aSlice) anItem := AsMap(aSlice[0]) assert.Equal(t, "k1", anItem["Key"]) assert.Equal(t, "v1", anItem["Value"]) assert.Equal(t, 2.0, anItem["Attr"]) } }