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  }