github.com/prebid/prebid-server@v0.275.0/stored_requests/backends/file_fetcher/fetcher.go (about) 1 package file_fetcher 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "strings" 9 10 "github.com/prebid/prebid-server/stored_requests" 11 jsonpatch "gopkg.in/evanphx/json-patch.v4" 12 ) 13 14 // NewFileFetcher _immediately_ loads stored request data from local files. 15 // These are stored in memory for low-latency reads. 16 // 17 // This expects each file in the directory to be named "{config_id}.json". 18 // For example, when asked to fetch the request with ID == "23", it will return the data from "directory/23.json". 19 func NewFileFetcher(directory string) (stored_requests.AllFetcher, error) { 20 storedData, err := collectStoredData(directory, FileSystem{make(map[string]FileSystem), make(map[string]json.RawMessage)}, nil) 21 return &eagerFetcher{storedData, nil}, err 22 } 23 24 type eagerFetcher struct { 25 FileSystem FileSystem 26 Categories map[string]map[string]stored_requests.Category 27 } 28 29 func (fetcher *eagerFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (map[string]json.RawMessage, map[string]json.RawMessage, []error) { 30 storedRequests := fetcher.FileSystem.Directories["stored_requests"].Files 31 storedImpressions := fetcher.FileSystem.Directories["stored_imps"].Files 32 errs := appendErrors("Request", requestIDs, storedRequests, nil) 33 errs = appendErrors("Imp", impIDs, storedImpressions, errs) 34 return storedRequests, storedImpressions, errs 35 } 36 37 func (fetcher *eagerFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 38 return nil, nil 39 } 40 41 // FetchAccount fetches the host account configuration for a publisher 42 func (fetcher *eagerFetcher) FetchAccount(ctx context.Context, accountDefaultsJSON json.RawMessage, accountID string) (json.RawMessage, []error) { 43 if len(accountID) == 0 { 44 return nil, []error{fmt.Errorf("Cannot look up an empty accountID")} 45 } 46 accountJSON, ok := fetcher.FileSystem.Directories["accounts"].Files[accountID] 47 if !ok { 48 return nil, []error{stored_requests.NotFoundError{ 49 ID: accountID, 50 DataType: "Account", 51 }} 52 } 53 54 completeJSON, err := jsonpatch.MergePatch(accountDefaultsJSON, accountJSON) 55 if err != nil { 56 return nil, []error{err} 57 } 58 return completeJSON, nil 59 } 60 61 func (fetcher *eagerFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { 62 fileName := primaryAdServer 63 64 if len(publisherId) != 0 { 65 fileName = primaryAdServer + "_" + publisherId 66 } 67 68 if fetcher.Categories == nil { 69 fetcher.Categories = make(map[string]map[string]stored_requests.Category) 70 } 71 if data, ok := fetcher.Categories[fileName]; ok { 72 return data[iabCategory].Id, nil 73 } 74 75 if primaryAdServerDir, found := fetcher.FileSystem.Directories[primaryAdServer]; found { 76 77 if file, ok := primaryAdServerDir.Files[fileName]; ok { 78 79 tmp := make(map[string]stored_requests.Category) 80 81 if err := json.Unmarshal(file, &tmp); err != nil { 82 return "", fmt.Errorf("Unable to unmarshal categories for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) 83 } 84 fetcher.Categories[fileName] = tmp 85 resultCategory := tmp[iabCategory].Id 86 primaryAdServerDir.Files[fileName] = nil 87 88 if len(resultCategory) == 0 { 89 return "", fmt.Errorf("Unable to find category for adserver '%s', publisherId: '%s', iab category: '%s'", primaryAdServer, publisherId, iabCategory) 90 } 91 return resultCategory, nil 92 } else { 93 return "", fmt.Errorf("Unable to find mapping file for adserver: '%s', publisherId: '%s'", primaryAdServer, publisherId) 94 95 } 96 97 } 98 99 return "", fmt.Errorf("Category '%s' not found for server: '%s', publisherId: '%s'", 100 iabCategory, primaryAdServer, publisherId) 101 102 } 103 104 type FileSystem struct { 105 Directories map[string]FileSystem 106 Files map[string]json.RawMessage 107 } 108 109 func collectStoredData(directory string, fileSystem FileSystem, err error) (FileSystem, error) { 110 if err != nil { 111 return FileSystem{nil, nil}, err 112 } 113 fileInfos, err := os.ReadDir(directory) 114 if err != nil { 115 return FileSystem{nil, nil}, err 116 } 117 data := make(map[string]json.RawMessage) 118 119 for _, fileInfo := range fileInfos { 120 if fileInfo.IsDir() { 121 122 fs := FileSystem{make(map[string]FileSystem), make(map[string]json.RawMessage)} 123 fileSys, innerErr := collectStoredData(directory+"/"+fileInfo.Name(), fs, err) 124 if innerErr != nil { 125 return FileSystem{nil, nil}, innerErr 126 } 127 fileSystem.Directories[fileInfo.Name()] = fileSys 128 129 } else { 130 if strings.HasSuffix(fileInfo.Name(), ".json") { // Skip the .gitignore 131 fileData, err := os.ReadFile(fmt.Sprintf("%s/%s", directory, fileInfo.Name())) 132 if err != nil { 133 return FileSystem{nil, nil}, err 134 } 135 data[strings.TrimSuffix(fileInfo.Name(), ".json")] = json.RawMessage(fileData) 136 137 } 138 } 139 140 } 141 fileSystem.Files = data 142 return fileSystem, err 143 } 144 145 func appendErrors(dataType string, ids []string, data map[string]json.RawMessage, errs []error) []error { 146 for _, id := range ids { 147 if _, ok := data[id]; !ok { 148 errs = append(errs, stored_requests.NotFoundError{ 149 ID: id, 150 DataType: dataType, 151 }) 152 } 153 } 154 return errs 155 }