ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge remote-tracking branch 'origin/master' into gpt55switch

+4518 -6807
+4
cmd/albertsonsquery/main.go
··· 10 10 11 11 "careme/internal/albertsons/query" 12 12 "careme/internal/brightdata" 13 + 14 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 13 15 ) 14 16 15 17 func main() { ··· 57 59 ctx, cancel = context.WithTimeout(ctx, time.Duration(timeoutSec)*time.Second) 58 60 defer cancel() 59 61 } 62 + 60 63 httpClient, err := brightdata.NewProxyAwareHTTPClient(brightdata.LoadConfig()) 61 64 if err != nil { 62 65 return fmt.Errorf("create HTTP client: %w", err) 63 66 } 67 + httpClient.Transport = otelhttp.NewTransport(httpClient.Transport) 64 68 client, err := query.NewSearchClient(query.SearchClientConfig{ 65 69 BaseURL: baseURL, 66 70 SubscriptionKey: subscriptionKey,
+5 -3
cmd/careme/web.go
··· 29 29 "careme/internal/watchdog" 30 30 31 31 cachepkg "careme/internal/cache" 32 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 32 33 ) 33 34 34 35 func runServer(cfg *config.Config, addr string) error { ··· 58 59 userStorage := users.NewStorage(cache) 59 60 ro := &readyOnce{} 60 61 watchdogServer := watchdog.Server{} 62 + aiHTTPClient := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} 61 63 // TODO make the mock more transparent? 62 - grader := ingredientgrading.NewManager(cfg, cache) 64 + grader := ingredientgrading.NewManager(cfg, cache, aiHTTPClient) 63 65 64 66 var generator recipes.ExtGenerator 65 67 var waitFns []func() 66 68 if cfg.Mocks.Enable { 67 69 generator = recipes.NewMockGenerator() 68 70 } else { 69 - mc := critique.NewManager(cfg, cache) 71 + mc := critique.NewManager(cfg, cache, aiHTTPClient) 70 72 ro.add(mc) 71 73 72 - aiclient := ai.NewClient(cfg.AI.APIKey, "TODOMODEL") 74 + aiclient := ai.NewClient(cfg.AI.APIKey, "TODOMODEL", aiHTTPClient) 73 75 ro.add(aiclient) 74 76 staples, err := recipes.NewCachedStaplesService(cfg, cache, grader) 75 77 if err != nil {
+36 -52
cmd/ingredients/main.go
··· 5 5 "flag" 6 6 "fmt" 7 7 "log" 8 + "net/http" 8 9 "slices" 9 10 "strings" 10 11 ··· 20 21 func main() { 21 22 var searchTerm string 22 23 var location string 24 + var verbose bool 23 25 flag.StringVar(&searchTerm, "ingredient", "", "Search term for ingredient lookup") 24 26 flag.StringVar(&searchTerm, "i", "", "Search term for ingredient lookup") 25 27 flag.StringVar(&location, "location", "", "Location for recipe sourcing (e.g., 70100023)") 26 28 flag.StringVar(&location, "l", "", "Location for recipe sourcing (short form)") 29 + flag.BoolVar(&verbose, "verbose", false, "dump all ingredients and grades") 27 30 flag.Parse() 28 31 ctx := context.Background() 29 32 33 + if searchTerm != "" { 34 + verbose = true 35 + } 36 + 30 37 cfg, err := config.Load() 31 38 if err != nil { 32 39 log.Fatalf("failed to load configuration: %s", err) ··· 48 55 } 49 56 50 57 catMap := make(map[string]int) 51 - if cfg.IngredientGrading.Enable { 52 - log.Printf("Grading %d ingredients", len(ings)) 53 - cacheStore, err := cache.MakeCache() 54 - if err != nil { 55 - log.Fatalf("failed to create cache for ingredient grading: %s", err) 56 - } 57 - grader := ingredientgrading.NewManager(cfg, cacheStore) 58 - graded, err := grader.GradeIngredients(ctx, ings) 59 - if err != nil { 60 - log.Fatalf("failed to grade ingredients: %s", err) 61 - } 62 - slices.SortFunc(graded, func(a, b ai.InputIngredient) int { 63 - if a.Grade.Score != b.Grade.Score { 64 - return b.Grade.Score - a.Grade.Score 65 - } 66 - return strings.Compare(strings.ToLower(a.Description), strings.ToLower(b.Description)) 67 - }) 68 - for _, result := range graded { 69 - for _, cat := range result.Categories { 70 - catMap[cat] += 1 71 - } 72 58 73 - fmt.Printf("%2d/10: %s - %s: size: %s: %s\n", result.Grade.Score, result.Brand, result.Description, result.Size, result.Grade.Reason) 74 - } 75 - for cat, count := range catMap { 76 - fmt.Printf("Category: %s, Count: %d\n", cat, count) 77 - } 78 - 79 - counts := lo.Reduce(graded, func(counts map[int]int, ingredient ai.InputIngredient, _ int) map[int]int { 80 - counts[ingredient.Grade.Score] += 1 81 - return counts 82 - }, make(map[int]int)) 83 - fmt.Println("Grade distribution:") 84 - for score := 0; score <= 10; score++ { 85 - fmt.Printf("Score %2d: %d ingredients\n", score, counts[score]) 86 - } 87 - return 59 + log.Printf("Grading %d ingredients", len(ings)) 60 + cacheStore, err := cache.MakeCache() 61 + if err != nil { 62 + log.Fatalf("failed to create cache for ingredient grading: %s", err) 88 63 } 89 - 90 - for _, i := range ings { 91 - for _, cat := range categories(i) { 64 + grader := ingredientgrading.NewManager(cfg, cacheStore, http.DefaultClient) 65 + graded, err := grader.GradeIngredients(ctx, ings) 66 + if err != nil { 67 + log.Fatalf("failed to grade ingredients: %s", err) 68 + } 69 + slices.SortFunc(graded, func(a, b ai.InputIngredient) int { 70 + if a.Grade.Score != b.Grade.Score { 71 + return b.Grade.Score - a.Grade.Score 72 + } 73 + return strings.Compare(strings.ToLower(a.Description), strings.ToLower(b.Description)) 74 + }) 75 + for _, result := range graded { 76 + for _, cat := range result.Categories { 92 77 catMap[cat] += 1 93 78 } 94 - fmt.Printf("%s: %s - %s:($%s) size: %s categories: %v\n", i.ProductID, i.Brand, i.Description, toFloat(i.PriceRegular), i.Size, i.Categories) 79 + if verbose { 80 + fmt.Printf("%2d/10: %s - %s: size: %s: %s\n", result.Grade.Score, result.Brand, result.Description, result.Size, result.Grade.Reason) 81 + } 95 82 } 96 83 for cat, count := range catMap { 97 84 fmt.Printf("Category: %s, Count: %d\n", cat, count) 98 85 } 99 - } 100 86 101 - func toFloat(f *float32) string { 102 - if f == nil { 103 - return "" 104 - } 105 - return fmt.Sprintf("%.2f", *f) 106 - } 107 - 108 - func categories(i ai.InputIngredient) []string { 109 - if i.Categories == nil { 110 - return nil 87 + counts := lo.Reduce(graded, func(counts map[int]int, ingredient ai.InputIngredient, _ int) map[int]int { 88 + counts[ingredient.Grade.Score] += 1 89 + return counts 90 + }, make(map[int]int)) 91 + fmt.Println("Grade distribution:") 92 + for score := range 10 { 93 + fmt.Printf("Score %2d: %d ingredients\n", score, counts[score]) 111 94 } 112 - return i.Categories 95 + sumGrades := lo.SumBy(graded, func(ing ai.InputIngredient) int { return ing.Grade.Score }) 96 + fmt.Printf("Total count %d and score %d\n", len(graded), sumGrades) 113 97 }
+2
go.mod
··· 20 20 github.com/sendgrid/sendgrid-go v3.16.1+incompatible 21 21 github.com/stretchr/testify v1.11.1 22 22 go.opentelemetry.io/contrib/bridges/otelslog v0.18.0 23 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 23 24 go.opentelemetry.io/otel v1.43.0 24 25 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 25 26 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 ··· 48 49 github.com/cespare/xxhash/v2 v2.3.0 // indirect 49 50 github.com/davecgh/go-spew v1.1.1 // indirect 50 51 github.com/emicklei/go-restful/v3 v3.12.2 // indirect 52 + github.com/felixge/httpsnoop v1.0.4 // indirect 51 53 github.com/fxamacker/cbor/v2 v2.9.0 // indirect 52 54 github.com/go-jose/go-jose/v3 v3.0.4 // indirect 53 55 github.com/go-logr/logr v1.4.3 // indirect
+4
go.sum
··· 61 61 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 62 62 github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 63 63 github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 64 + github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 65 + github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 64 66 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 65 67 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 66 68 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= ··· 282 284 go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 283 285 go.opentelemetry.io/contrib/bridges/otelslog v0.18.0 h1:hhPGP3zvvy1xWT9RTy970wlniSxFttBIsAK1gvMguJM= 284 286 go.opentelemetry.io/contrib/bridges/otelslog v0.18.0/go.mod h1:twJF7inoMza6kxMcF8JOdL3mPmtOZu7GEr34CUNE6Dg= 287 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= 288 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= 285 289 go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= 286 290 go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= 287 291 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
+17 -36
internal/actowiz/staples.go
··· 10 10 "strconv" 11 11 "strings" 12 12 13 - "careme/internal/kroger" 13 + "careme/internal/ai" 14 14 ) 15 15 16 16 const LocationIDPrefix = "safeway_" ··· 47 47 return storeID != "" && storeID != locationID 48 48 } 49 49 50 - func all() []kroger.Ingredient { 50 + func all() []ai.InputIngredient { 51 51 // do this once instead of every time? 52 - ingredients := make([]kroger.Ingredient, 0, len(embeddedSafewayProducts)) 52 + ingredients := make([]ai.InputIngredient, 0, len(embeddedSafewayProducts)) 53 53 for _, product := range embeddedSafewayProducts { 54 54 if !product.Availability { 55 55 continue ··· 68 68 return ingredients 69 69 } 70 70 71 - func (p StaplesProvider) FetchStaples(_ context.Context, locationID string) ([]kroger.Ingredient, error) { 71 + func (p StaplesProvider) FetchStaples(_ context.Context, locationID string) ([]ai.InputIngredient, error) { 72 72 if !p.IsID(locationID) { 73 73 return nil, fmt.Errorf("invalid safeway location id %q", locationID) 74 74 } 75 75 return all(), nil 76 76 } 77 77 78 - func (p StaplesProvider) GetIngredients(_ context.Context, locationID string, searchTerm string, _ int) ([]kroger.Ingredient, error) { 78 + func (p StaplesProvider) GetIngredients(_ context.Context, locationID string, searchTerm string, _ int) ([]ai.InputIngredient, error) { 79 79 if !p.IsID(locationID) { 80 80 return nil, fmt.Errorf("invalid safeway location id %q", locationID) 81 81 } ··· 83 83 return filterIngredients(all(), searchTerm), nil 84 84 } 85 85 86 - func filterIngredients(ingredients []kroger.Ingredient, searchTerm string) []kroger.Ingredient { 86 + func filterIngredients(ingredients []ai.InputIngredient, searchTerm string) []ai.InputIngredient { 87 87 searchTerm = strings.TrimSpace(strings.ToLower(searchTerm)) 88 88 if searchTerm == "" { 89 89 return slices.Clone(ingredients) 90 90 } 91 91 92 - filtered := make([]kroger.Ingredient, 0, len(ingredients)) 92 + filtered := make([]ai.InputIngredient, 0, len(ingredients)) 93 93 for _, ingredient := range ingredients { 94 94 if ingredientMatches(ingredient, searchTerm) { 95 95 filtered = append(filtered, ingredient) ··· 98 98 return filtered 99 99 } 100 100 101 - func ingredientMatches(ingredient kroger.Ingredient, searchTerm string) bool { 101 + func ingredientMatches(ingredient ai.InputIngredient, searchTerm string) bool { 102 102 for _, value := range []string{ 103 - stringValue(ingredient.Description), 104 - stringValue(ingredient.Brand), 103 + ingredient.Description, 104 + ingredient.Brand, 105 105 } { 106 106 if strings.Contains(strings.ToLower(value), searchTerm) { 107 107 return true ··· 111 111 return false 112 112 } 113 113 114 - func productToIngredient(product SafewayProduct) kroger.Ingredient { 114 + func productToIngredient(product SafewayProduct) ai.InputIngredient { 115 115 description, size := splitProductName(product.ProductName) // dubious size is really always 116 116 regularPrice := float32Ptr(product.MRP) 117 117 salePrice := float32Ptr(product.DiscountedPrice) ··· 121 121 122 122 productID := strconv.FormatInt(product.ID, 10) 123 123 categories := compactStrings([]string{product.Category, product.SubCategory}) 124 - var categoryPtr *[]string 125 - if len(categories) > 0 { 126 - categoryPtr = &categories 127 - } 128 124 129 - return kroger.Ingredient{ 130 - ProductId: stringPtr(productID), 131 - Description: stringPtr(description), 132 - Size: stringPtr(size), 125 + return ai.NormalizeInputIngredient(ai.InputIngredient{ 126 + ProductID: productID, 127 + Description: description, 128 + Size: size, 133 129 PriceRegular: regularPrice, 134 130 PriceSale: salePrice, 135 - Categories: categoryPtr, 136 - } 131 + Categories: categories, 132 + }) 137 133 } 138 134 139 135 func splitProductName(name string) (string, string) { ··· 163 159 panic(fmt.Errorf("decode safeway products: %w", err)) 164 160 } 165 161 return products 166 - } 167 - 168 - func stringValue(value *string) string { 169 - if value == nil { 170 - return "" 171 - } 172 - return *value 173 - } 174 - 175 - func stringPtr(value string) *string { 176 - value = strings.TrimSpace(value) 177 - if value == "" { 178 - return nil 179 - } 180 - return &value 181 162 } 182 163 183 164 func float32Ptr(value *float64) *float32 {
+5 -5
internal/actowiz/staples_test.go
··· 25 25 } 26 26 27 27 first := got[0] 28 - if first.ProductId == nil || *first.ProductId == "" { 29 - t.Fatalf("unexpected product id: %+v", first.ProductId) 28 + if first.ProductID == "" { 29 + t.Fatalf("unexpected product id: %+v", first.ProductID) 30 30 } 31 - if first.Description == nil || *first.Description == "" { 31 + if first.Description == "" { 32 32 t.Fatalf("unexpected description: %+v", first.Description) 33 33 } 34 - if first.Categories == nil || len(*first.Categories) == 0 { 34 + if len(first.Categories) == 0 { 35 35 t.Fatalf("unexpected categories: %+v", first.Categories) 36 36 } 37 37 } ··· 58 58 if len(got) == 0 { 59 59 t.Fatal("expected filtered ingredients after skip") 60 60 } 61 - if got[0].Description == nil { 61 + if got[0].Description == "" { 62 62 t.Fatalf("missing description: %+v", got[0]) 63 63 } 64 64 }
+25 -25
internal/ai/client.go
··· 8 8 "hash/fnv" 9 9 "io" 10 10 "log/slog" 11 + "net/http" 11 12 "strings" 12 13 "time" 13 14 14 - "careme/internal/locations" 15 + locationtypes "careme/internal/locations/types" 15 16 16 17 openai "github.com/openai/openai-go/v3" 17 18 "github.com/openai/openai-go/v3/option" ··· 20 21 21 22 "github.com/invopop/jsonschema" 22 23 ) 23 - 24 - type client struct { 25 - apiKey string 26 - schema map[string]any 27 - wineSchema map[string]any 28 - model string 29 - wineModel string 30 - } 31 24 32 25 type GeneratedImage struct { 33 26 Body io.Reader ··· 104 97 Commentary string `json:"commentary"` 105 98 } 106 99 100 + type client struct { 101 + schema map[string]any 102 + wineSchema map[string]any 103 + model string 104 + wineModel string 105 + oai openai.Client 106 + } 107 + 107 108 // ignoring model for now. 108 - func NewClient(apiKey, _ string) *client { 109 + func NewClient(apiKey, _ string, httpClient *http.Client) *client { 109 110 // ignor model for now. 110 111 r := jsonschema.Reflector{ 111 112 DoNotReference: true, // no $defs and no $ref ··· 119 120 _ = json.Unmarshal(recipesSchemaJSON, &m) 120 121 var wine map[string]any 121 122 _ = json.Unmarshal(wineSchemaJSON, &wine) 123 + opts := []option.RequestOption{option.WithAPIKey(apiKey)} 124 + if httpClient != nil { 125 + opts = append(opts, option.WithHTTPClient(httpClient)) 126 + } 127 + aiClient := openai.NewClient(opts...) 122 128 return &client{ 123 - apiKey: apiKey, 129 + oai: aiClient, 124 130 schema: m, 125 131 wineSchema: wine, 126 132 model: defaultRecipeModel, ··· 220 226 if previousResponseID == "" { 221 227 return nil, fmt.Errorf("response ID is required for regeneration") 222 228 } 223 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 224 229 messages := cleanInstuctions(instructions) 225 230 226 231 params := responses.ResponseNewParams{ ··· 233 238 Store: openai.Bool(true), 234 239 Text: scheme(c.schema), 235 240 } 236 - resp, err := client.Responses.New(ctx, params) 241 + resp, err := c.oai.Responses.New(ctx, params) 237 242 if err != nil { 238 243 return nil, fmt.Errorf("failed to regenerate recipes: %w", err) 239 244 } ··· 246 251 if question == "" { 247 252 return nil, fmt.Errorf("question is required") 248 253 } 249 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 250 254 251 255 params := responses.ResponseNewParams{ 252 256 Model: c.model, ··· 259 263 if previousResponseID != "" { 260 264 params.PreviousResponseID = openai.String(previousResponseID) 261 265 } 262 - resp, err := client.Responses.New(ctx, params) 266 + resp, err := c.oai.Responses.New(ctx, params) 263 267 if err != nil { 264 268 return nil, fmt.Errorf("failed to answer question: %w", err) 265 269 } ··· 282 286 return nil, fmt.Errorf("failed to build recipe image prompt: %w", err) 283 287 } 284 288 285 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 286 - resp, err := client.Images.Generate(ctx, openai.ImageGenerateParams{ 289 + resp, err := c.oai.Images.Generate(ctx, openai.ImageGenerateParams{ 287 290 Prompt: prompt, 288 291 Model: recipeImageModel, 289 292 N: openai.Int(1), ··· 344 347 if err != nil { 345 348 return nil, fmt.Errorf("failed to build wine selection prompt: %w", err) 346 349 } 347 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 348 350 params := responses.ResponseNewParams{ 349 351 Model: c.wineModel, 350 352 Instructions: openai.String(winePrompt), ··· 353 355 }, 354 356 Text: scheme(c.wineSchema), 355 357 } 356 - resp, err := client.Responses.New(ctx, params) 358 + resp, err := c.oai.Responses.New(ctx, params) 357 359 if err != nil { 358 360 return nil, fmt.Errorf("failed to pick wine: %w", err) 359 361 } ··· 365 367 return &selection, nil 366 368 } 367 369 368 - func (c *client) GenerateRecipes(ctx context.Context, location *locations.Location, saleIngredients []InputIngredient, instructions []string, date time.Time, lastRecipes []string) (*ShoppingList, error) { 370 + func (c *client) GenerateRecipes(ctx context.Context, location *locationtypes.Location, saleIngredients []InputIngredient, instructions []string, date time.Time, lastRecipes []string) (*ShoppingList, error) { 369 371 messages, err := c.buildRecipeMessages(location, saleIngredients, instructions, date, lastRecipes) 370 372 if err != nil { 371 373 return nil, fmt.Errorf("failed to build recipe messages: %w", err) 372 374 } 373 375 374 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 375 376 params := responses.ResponseNewParams{ 376 377 Model: c.model, 377 378 Instructions: openai.String(systemMessage), ··· 384 385 } 385 386 // should we stream. Can we pass past generation. 386 387 387 - resp, err := client.Responses.New(ctx, params) 388 + resp, err := c.oai.Responses.New(ctx, params) 388 389 if err != nil { 389 390 return nil, fmt.Errorf("failed to generate recipes: %w", err) 390 391 } ··· 431 432 } 432 433 433 434 // buildRecipeMessages creates separate messages for the LLM to process more efficiently 434 - func (c *client) buildRecipeMessages(location *locations.Location, saleIngredients []InputIngredient, instructions []string, date time.Time, lastRecipes []string) (responses.ResponseInputParam, error) { 435 + func (c *client) buildRecipeMessages(location *locationtypes.Location, saleIngredients []InputIngredient, instructions []string, date time.Time, lastRecipes []string) (responses.ResponseInputParam, error) { 435 436 var messages []responses.ResponseInputItemUnionParam 436 437 // constants we might make variable later 437 438 messages = append(messages, user("Prioritize ingredients that are in season for the current date and user's state location "+date.Format("January 2nd")+" in "+location.State+".")) ··· 469 470 // more CORRECT to do a very simple response request with allowed tokens 1 but this seems cheaper 470 471 // https://chatgpt.com/share/6984da16-ff88-8009-8486-4e0479ac6a01 471 472 // could only do it once to ensure startup 472 - client := openai.NewClient(option.WithAPIKey(c.apiKey)) 473 - _, err := client.Models.List(ctx) 473 + _, err := c.oai.Models.List(ctx) 474 474 return err 475 475 } 476 476
+22 -21
internal/ai/critique.go
··· 5 5 "encoding/json" 6 6 "fmt" 7 7 "log/slog" 8 + "net/http" 8 9 "strings" 9 10 "time" 10 11 ··· 50 51 } 51 52 52 53 type critiquer struct { 53 - apiKey string 54 54 model string 55 55 schema map[string]any 56 + gem *genai.Client 56 57 } 57 58 58 - func NewCritiquer(apiKey, model string) *critiquer { 59 + func NewCritiquer(apiKey, model string, httpClient *http.Client) *critiquer { 59 60 model = strings.TrimSpace(model) 60 61 if model == "" { 61 62 model = defaultGeminiCritiqueModel 62 63 } 64 + 65 + if httpClient == nil { 66 + httpClient = http.DefaultClient 67 + } 68 + 69 + // pass in context and return error? seems like context only used in edge case 70 + client, err := genai.NewClient(context.TODO(), &genai.ClientConfig{ 71 + APIKey: apiKey, 72 + Backend: genai.BackendGeminiAPI, 73 + HTTPClient: httpClient, 74 + }) 75 + if err != nil { 76 + panic(err) 77 + } 78 + 63 79 return &critiquer{ 64 - apiKey: strings.TrimSpace(apiKey), 80 + gem: client, 65 81 model: model, 66 82 schema: recipeCritiqueJSONSchema(), 67 83 } 68 84 } 69 85 70 86 func (c *critiquer) Ready(ctx context.Context) error { 71 - client, err := c.newClient(ctx) 72 - if err != nil { 73 - return err 74 - } 75 - for _, err := range client.Models.All(ctx) { 87 + for _, err := range c.gem.Models.All(ctx) { 76 88 return err 77 89 } 78 90 return fmt.Errorf("model not found: %s", c.model) ··· 95 107 if err != nil { 96 108 return nil, fmt.Errorf("failed to build recipe critique prompt: %w", err) 97 109 } 98 - client, err := c.newClient(ctx) 110 + 99 111 if err != nil { 100 112 return nil, err 101 113 } 102 114 start := time.Now() 103 - resp, err := client.Models.GenerateContent(ctx, c.model, genai.Text(prompt), &genai.GenerateContentConfig{ 115 + resp, err := c.gem.Models.GenerateContent(ctx, c.model, genai.Text(prompt), &genai.GenerateContentConfig{ 104 116 SystemInstruction: genai.NewContentFromText(recipeCritiqueSystemInstruction, genai.RoleUser), 105 117 // Temperature: genai.Ptr[float32](0), 106 118 // MaxOutputTokens: 768, ··· 146 158 } 147 159 148 160 return slog.Group("usage", attrs...) 149 - } 150 - 151 - func (c *critiquer) newClient(ctx context.Context) (*genai.Client, error) { 152 - client, err := genai.NewClient(ctx, &genai.ClientConfig{ 153 - APIKey: c.apiKey, 154 - Backend: genai.BackendGeminiAPI, 155 - }) 156 - if err != nil { 157 - return nil, fmt.Errorf("create Gemini client: %w", err) 158 - } 159 - return client, nil 160 161 } 161 162 162 163 func parseRecipeCritique(body string) (*RecipeCritique, error) {
+34 -6
internal/ai/ingredient_grade.go
··· 8 8 "hash/fnv" 9 9 "io" 10 10 "log/slog" 11 + "net/http" 11 12 "strings" 12 13 13 14 "github.com/invopop/jsonschema" ··· 21 22 defaultIngredientGradeModel = openai.ChatModelGPT5Mini 22 23 ) 23 24 25 + // should we have category spefic grading prompts? 24 26 const ingredientGradeSystemInstruction = ` 25 27 You review grocery catalog items before they are shown to a home recipe generator. 26 28 ··· 37 39 - formats intended mainly for snacking or immediate eating rather than cooking 38 40 - pre-cut fruit unless it is still broadly useful for cooking or baking 39 41 42 + Additional rules for pasta, grains, rice, legumes, and noodles: 43 + 44 + - Prefer flexible base carbohydrates: 45 + rice, dry pasta, oats, quinoa, farro, freekah 46 + 47 + - Use simple score anchors: 48 + standard dry pasta → 6–7 49 + premium dry pasta → 8–9 50 + alternative pasta (chickpea, lentil, gluten-free) → 5–6 51 + bread → 5–6 52 + prepared sauces → max 6 53 + instant or flavored mixes → 3–5 54 + 55 + - Reward real cooking-performance signals: 56 + bronze-cut, slow-dried, high-protein durum, whole grain, hulled, pearled 57 + 58 + - Reward known higher-quality brands (e.g., Felicetti, De Cecco, Rummo, Rustichella) 59 + 60 + - Do not infer quality from generic terms: 61 + "quality", "non-GMO", "organic", "traditional" 62 + 63 + - Penalize items that are less flexible or more processed 40 64 41 65 Scoring anchors: 42 66 - 9-10: excellent raw/fresh flexible cooking ingredient, e.g. whole vegetables, greens, roots, raw meats, fresh fruit useful in baking/cooking ··· 50 74 51 75 Return JSON only. Preserve each input id/index exactly. Be concise.` 52 76 53 - // this is wire compatible with kroger.Ingredient eventually it should replace it in what staples returns 54 77 type InputIngredient struct { 55 78 ProductID string `json:"id,omitempty"` 56 79 AisleNumber string `json:"number,omitempty"` // this is a dumb json name fix it later ··· 98 121 } 99 122 100 123 type ingredientGrader struct { 101 - apiKey string 102 124 model string 103 125 cacheVersion string 104 126 schema map[string]any 127 + oai openai.Client 105 128 } 106 129 107 130 func ingredientGradeCacheVersion(model, systemInstruction string) string { ··· 111 134 return base64.RawURLEncoding.EncodeToString(fnv.Sum(nil)) 112 135 } 113 136 114 - func NewIngredientGrader(apiKey, model string) *ingredientGrader { 137 + func NewIngredientGrader(apiKey, model string, httpClient *http.Client) *ingredientGrader { 115 138 model = strings.TrimSpace(model) 116 139 if model == "" { 117 140 model = defaultIngredientGradeModel 118 141 } 142 + opts := []option.RequestOption{option.WithAPIKey(apiKey)} 143 + if httpClient != nil { 144 + opts = append(opts, option.WithHTTPClient(httpClient)) 145 + } 146 + aiClient := openai.NewClient(opts...) 147 + 119 148 return &ingredientGrader{ 120 - apiKey: strings.TrimSpace(apiKey), 149 + oai: aiClient, 121 150 model: model, 122 151 cacheVersion: ingredientGradeCacheVersion(model, ingredientGradeSystemInstruction), 123 152 schema: ingredientGradeJSONSchema(), ··· 147 176 return nil, fmt.Errorf("failed to build ingredient grading prompt: %w", err) 148 177 } 149 178 150 - client := openai.NewClient(option.WithAPIKey(g.apiKey)) 151 - resp, err := client.Responses.New(ctx, responses.ResponseNewParams{ 179 + resp, err := g.oai.Responses.New(ctx, responses.ResponseNewParams{ 152 180 Model: g.model, 153 181 Instructions: openai.String(ingredientGradeSystemInstruction), 154 182 Input: responses.ResponseNewParamsInputUnion{
+14 -6
internal/albertsons/query/client.go
··· 12 12 "time" 13 13 ) 14 14 15 + // this is a strange set. Actual sub categories don't work but thes aisle-vs ones do. 16 + // for en example broke sub category here is beef https://www.safeway.com/shop/aisles/meat-seafood/beef.html?sort=&page=1&loc=1142 15 17 const ( 16 - Category_Vegatables = "GR-C-categ-8c62c848" 17 - Category_Fruit = "GR-C-categ-a8eea474" 18 - Category_Seafood = "GR-C-Categ-6090cd27" 19 - Category_Meat = "GR-MeatF-fffc8662" 20 - Category_Wine = "GR-S-Searc-db592d50" 18 + Category_Vegatables = "GR-C-categ-8c62c848" 19 + Category_Fruit = "GR-C-categ-a8eea474" 20 + Category_Seafood = "GR-C-Categ-6090cd27" // https://www.safeway.com/aisle-vs/meat-seafood/seafood-favorites.html 21 + Category_Meat = "GR-MeatF-fffc8662" // https://www.safeway.com/aisle-vs/meat-seafood/meat-favorites.html 22 + Category_Wine = "GR-S-Searc-db592d50" 23 + Category_Pasta_Grains = "GR-C-Categ-77b9d5dd" // https://www.safeway.com/aisle-vs/grains-pasta-sides/best-sellers.html 24 + Category_Dairy = "GR-C-Categ-f210e5cd" // new and trending seems dubious https://www.safeway.com/aisle-vs/dairy-eggs-cheese/new-trending.html 21 25 ) 22 26 23 27 func StapleCategories() []string { ··· 26 30 Category_Fruit, 27 31 Category_Seafood, 28 32 Category_Meat, 33 + Category_Pasta_Grains, 29 34 } 30 35 } 31 36 ··· 72 77 73 78 httpClient := cfg.HTTPClient 74 79 if httpClient == nil { 75 - httpClient = &http.Client{Timeout: 20 * time.Second} 80 + httpClient = http.DefaultClient 76 81 } 77 82 78 83 return &SearchClient{ ··· 84 89 } 85 90 86 91 func (c *SearchClient) Search(ctx context.Context, storeID, category string, opts SearchOptions) (*PathwaySearchPayload, error) { 92 + ctx, cancel := context.WithTimeout(ctx, time.Second*20) 93 + defer cancel() 94 + 87 95 storeID = strings.TrimSpace(storeID) 88 96 if storeID == "" { 89 97 return nil, errors.New("store id is required")
+25 -45
internal/albertsons/staples.go
··· 8 8 "net/http" 9 9 "strings" 10 10 11 + "careme/internal/ai" 11 12 "careme/internal/albertsons/query" 12 13 "careme/internal/cache" 13 14 "careme/internal/config" 14 - "careme/internal/kroger" 15 15 "careme/internal/parallelism" 16 16 17 17 "github.com/samber/lo" ··· 73 73 } 74 74 75 75 var stapleRows = map[string]uint{ 76 - query.Category_Vegatables: 150, // do we need way more of this? 77 - query.Category_Fruit: 100, 78 - query.Category_Meat: 100, 79 - query.Category_Seafood: 60, 76 + query.Category_Vegatables: 150, // do we need way more of this? 77 + query.Category_Fruit: 100, 78 + query.Category_Meat: 100, 79 + query.Category_Seafood: 60, 80 + query.Category_Pasta_Grains: 100, //??? 80 81 } 81 82 82 - func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]kroger.Ingredient, error) { 83 + func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 83 84 client, storeID, err := p.clientForLocation(locationID) 84 85 if err != nil { 85 86 return nil, err 86 87 } 87 88 88 - return parallelism.Flatten(query.StapleCategories(), func(category string) ([]kroger.Ingredient, error) { 89 + return parallelism.Flatten(query.StapleCategories(), func(category string) ([]ai.InputIngredient, error) { 89 90 payload, err := client.Search(ctx, storeID, category, query.SearchOptions{ 90 91 // how many rows? different per category? Should we paginate 91 92 Rows: stapleRows[category], ··· 96 97 return nil, err 97 98 } 98 99 99 - ingredients := lo.Map(payload.Response.Docs, func(product query.PathwaySearchProduct, _ int) kroger.Ingredient { 100 - return productToIngredient(product) 101 - }) 100 + ingredients := lo.Map(payload.Response.Docs, productToIngredient) 102 101 slog.InfoContext(ctx, "found albertsons staples for category", "count", len(ingredients), "category", category, "location", locationID) 103 102 return ingredients, nil 104 103 }) 105 104 } 106 105 107 106 // since this is mostly used by wine it isn't actuallyt they helpful. 108 - func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]kroger.Ingredient, error) { 107 + func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 109 108 client, storeID, err := p.clientForLocation(locationID) 110 109 if err != nil { 111 110 return nil, err ··· 113 112 114 113 // should we just resturn all instead of search term? how many is this? 115 114 payload, err := client.Search(ctx, storeID, query.Category_Wine, query.SearchOptions{ 116 - Query: searchTerm, Rows: 100, 115 + Query: searchTerm, Rows: 100, Start: uint(skip), 117 116 }) 118 117 if err != nil { 119 118 return nil, err 120 119 } 121 - 122 - ingredients := lo.Map(payload.Response.Docs, func(product query.PathwaySearchProduct, _ int) kroger.Ingredient { 123 - return productToIngredient(product) 124 - }) 125 - if skip >= len(ingredients) { 126 - return []kroger.Ingredient{}, nil 127 - } 128 - return ingredients[skip:], nil 120 + return lo.Map(payload.Response.Docs, productToIngredient), nil 129 121 } 130 122 131 123 // clientForLocation takes a prefixed store id and looks up chaing base url and returnes unprefixed id. ··· 156 148 return "", "", false 157 149 } 158 150 159 - func productToIngredient(product query.PathwaySearchProduct) kroger.Ingredient { 160 - productID := stringPtr(strings.TrimSpace(product.ID)) 161 - description := stringPtr(strings.TrimSpace(product.Name)) 151 + func productToIngredient(product query.PathwaySearchProduct, _ int) ai.InputIngredient { 152 + productID := strings.TrimSpace(product.ID) 153 + description := strings.TrimSpace(product.Name) 162 154 size := sizeText(product) 163 155 regularPrice := float32Ptr(product.BasePrice) 164 156 salePrice := float32Ptr(product.Price) 157 + // how does shelf relate to aisle? 165 158 categories := lo.Compact([]string{product.DepartmentName, product.ShelfName}) 166 159 167 - var categoryPtr *[]string 168 - if len(categories) > 0 { 169 - categoryPtr = &categories 170 - } 171 - 172 - return kroger.Ingredient{ 173 - ProductId: productID, 160 + return ai.NormalizeInputIngredient(ai.InputIngredient{ 161 + ProductID: productID, 174 162 Description: description, 175 - Size: size, 163 + Size: size, // will product id smash this as a dedupe? 176 164 PriceRegular: regularPrice, 177 165 PriceSale: salePrice, 178 - Categories: categoryPtr, 179 - } 166 + Categories: categories, 167 + AisleNumber: product.AisleID, // also an aisle name if thats better? 168 + }) 180 169 } 181 170 182 171 // this is a bit squirely shouldn't we take one ratehr than joiing both? 183 - func sizeText(product query.PathwaySearchProduct) *string { 172 + func sizeText(product query.PathwaySearchProduct) string { 184 173 sizeParts := lo.Compact([]string{product.ItemSizeQty, product.UnitOfMeasure}) 185 174 if len(sizeParts) == 0 { 186 - return nil 175 + return "" 187 176 } 188 - size := strings.Join(sizeParts, " ") 189 - return &size 190 - } 191 - 192 - func stringPtr(value string) *string { 193 - value = strings.TrimSpace(value) 194 - if value == "" { 195 - return nil 196 - } 197 - return &value 177 + return strings.Join(sizeParts, " ") 198 178 } 199 179 200 180 func float32Ptr(value float64) *float32 {
+34 -13
internal/albertsons/staples_test.go
··· 30 30 results map[string]query.PathwaySearchPayload 31 31 mu sync.Mutex 32 32 calls []string 33 + starts []uint 33 34 } 34 35 35 36 func (s *stubSearchClient) Search(_ context.Context, storeID, category string, opts query.SearchOptions) (*query.PathwaySearchPayload, error) { 36 37 s.mu.Lock() 37 38 s.calls = append(s.calls, storeID+":"+category+":"+opts.Query) 39 + s.starts = append(s.starts, opts.Start) 38 40 s.mu.Unlock() 39 41 40 42 payload := s.results[category] ··· 47 49 return slices.Contains(s.calls, want) 48 50 } 49 51 52 + func (s *stubSearchClient) startForCall(want string) (uint, bool) { 53 + s.mu.Lock() 54 + defer s.mu.Unlock() 55 + for i, call := range s.calls { 56 + if call == want { 57 + return s.starts[i], true 58 + } 59 + } 60 + return 0, false 61 + } 62 + 50 63 func (s *stubSearchClient) callCount() int { 51 64 s.mu.Lock() 52 65 defer s.mu.Unlock() ··· 95 108 } 96 109 97 110 first := got[0] 98 - if first.ProductId == nil || *first.ProductId != "veg-1" { 99 - t.Fatalf("unexpected product id: %+v", first.ProductId) 111 + if first.ProductID != "veg-1" { 112 + t.Fatalf("unexpected product id: %+v", first.ProductID) 100 113 } 101 - if first.Description == nil || *first.Description != "Broccoli Crown" { 114 + if first.Description != "Broccoli Crown" { 102 115 t.Fatalf("unexpected description: %+v", first.Description) 103 116 } 104 - if first.Size == nil || *first.Size != "1 EA" { 117 + if first.Size != "1 EA" { 105 118 t.Fatalf("unexpected size: %+v", first.Size) 106 119 } 107 120 if first.PriceRegular == nil || *first.PriceRegular != float32(3.49) { ··· 110 123 if first.PriceSale == nil || *first.PriceSale != float32(2.99) { 111 124 t.Fatalf("unexpected sale price: %+v", first.PriceSale) 112 125 } 113 - if first.Categories == nil || !slices.Equal(*first.Categories, []string{"Produce", "Vegetables"}) { 126 + if !slices.Equal(first.Categories, []string{"Produce", "Vegetables"}) { 114 127 t.Fatalf("unexpected categories: %+v", first.Categories) 115 128 } 116 129 } ··· 158 171 if err != nil { 159 172 t.Fatalf("GetIngredients returned error: %v", err) 160 173 } 161 - if !client.hasCall("806:" + query.Category_Wine + ":pinot") { 174 + searchCall := "806:" + query.Category_Wine + ":pinot" 175 + if !client.hasCall(searchCall) { 162 176 t.Fatalf("missing expected search call") 163 177 } 164 - if len(got) != 1 { 165 - t.Fatalf("expected 1 ingredient after skip, got %d", len(got)) 178 + if got, ok := client.startForCall(searchCall); !ok || got != 1 { 179 + t.Fatalf("unexpected search start: got %d found %t", got, ok) 166 180 } 167 - if got[0].Description == nil || *got[0].Description != "Rose Radishes" { 181 + if len(got) != 2 { 182 + t.Fatalf("expected 2 ingredients, got %d", len(got)) 183 + } 184 + if got[0].Description != "Pinot Tomatoes" { 168 185 t.Fatalf("unexpected description: %+v", got[0].Description) 169 186 } 170 187 } ··· 173 190 t.Parallel() 174 191 175 192 var sawRequest bool 193 + const skip = 17 176 194 httpClient := &http.Client{ 177 195 Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { 178 196 sawRequest = true 179 197 if got, want := r.URL.Host, "www.acmemarkets.com"; got != want { 180 198 t.Fatalf("unexpected host: got %q want %q", got, want) 181 199 } 200 + if got, want := r.URL.Query().Get("start"), "17"; got != want { 201 + t.Fatalf("unexpected start query param: got %q want %q", got, want) 202 + } 182 203 if got, want := r.Header.Get("Ocp-Apim-Subscription-Key"), "test-sub-key"; got != want { 183 204 t.Fatalf("unexpected subscription key: got %q want %q", got, want) 184 205 } ··· 207 228 return query.NewSearchClient(querycfg) 208 229 }) 209 230 210 - got, err := provider.GetIngredients(t.Context(), "acmemarkets_806", "pinot", 1) 231 + got, err := provider.GetIngredients(t.Context(), "acmemarkets_806", "pinot", skip) 211 232 if err != nil { 212 233 t.Fatalf("GetIngredients returned error: %v", err) 213 234 } 214 235 if !sawRequest { 215 236 t.Fatal("expected injected HTTP client to be used") 216 237 } 217 - if len(got) != 1 { 218 - t.Fatalf("expected 1 ingredient after skip, got %d", len(got)) 238 + if len(got) != 2 { 239 + t.Fatalf("expected 2 ingredients, got %d", len(got)) 219 240 } 220 - if got[0].Description == nil || *got[0].Description != "Rose" { 241 + if got[0].Description != "Pinot Noir" { 221 242 t.Fatalf("unexpected description: %+v", got[0].Description) 222 243 } 223 244 }
+18 -8
internal/brightdata/proxy.go
··· 47 47 } 48 48 49 49 func NewProxyAwareHTTPClient(cfg ProxyConfig) (*http.Client, error) { 50 - client := &http.Client{} 51 - if !cfg.Enabled() { 52 - return withRetries(client), nil 50 + transport := http.DefaultTransport 51 + if cfg.Enabled() { 52 + var err error 53 + transport, err = newProxyTransport(cfg) 54 + if err != nil { 55 + return nil, err 56 + } 53 57 } 54 58 59 + client := &http.Client{Transport: transport} 60 + 61 + return withRetries(client), nil 62 + } 63 + 64 + func newProxyTransport(cfg ProxyConfig) (*http.Transport, error) { 55 65 rootCAs, err := proxyRootCAs() 56 66 if err != nil { 57 67 return nil, err ··· 64 74 "username", cfg.Username, 65 75 ) 66 76 67 - transport := http.DefaultTransport.(*http.Transport).Clone() 68 - transport.Proxy = http.ProxyURL(cfg.proxyURL()) 69 - transport.TLSClientConfig = &tls.Config{RootCAs: rootCAs} 70 - client.Transport = transport 71 - return withRetries(client), nil 77 + // this feels funny 78 + proxyTransport := http.DefaultTransport.(*http.Transport).Clone() 79 + proxyTransport.Proxy = http.ProxyURL(cfg.proxyURL()) 80 + proxyTransport.TLSClientConfig = &tls.Config{RootCAs: rootCAs} 81 + return proxyTransport, nil 72 82 } 73 83 74 84 // this would be nice but it logs all retries as errors which sets off alerts.
+3 -3
internal/brightdata/proxy_test.go
··· 68 68 69 69 transport, ok := retryTransport.Client.HTTPClient.Transport.(*http.Transport) 70 70 if !ok { 71 - t.Fatalf("expected wrapped base *http.Transport, got %T", retryTransport.Client.HTTPClient.Transport) 71 + t.Fatalf("expected proxy *http.Transport, got %T", retryTransport.Client.HTTPClient.Transport) 72 72 } 73 73 74 74 req, err := http.NewRequest(http.MethodGet, "https://www.example.com/products", nil) ··· 104 104 if !ok { 105 105 t.Fatalf("expected *retryablehttp.RoundTripper when proxy disabled, got %T", client.Transport) 106 106 } 107 - if retryTransport.Client.HTTPClient.Transport != nil { 108 - t.Fatalf("expected default base transport via nil transport, got %T", retryTransport.Client.HTTPClient.Transport) 107 + if retryTransport.Client.HTTPClient.Transport != http.DefaultTransport { 108 + t.Fatalf("expected default base transport, got %T", retryTransport.Client.HTTPClient.Transport) 109 109 } 110 110 } 111 111
+14 -3
internal/cache/azure.go
··· 6 6 "io" 7 7 "log" 8 8 "log/slog" 9 + "net/http" 9 10 "os" 10 11 "strings" 11 12 12 13 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 14 + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" 13 15 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" 14 16 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" 15 17 "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" ··· 22 24 23 25 var _ ListCache = (*BlobCache)(nil) 24 26 25 - func NewBlobCache(container string) (*BlobCache, error) { 27 + func NewBlobCache(container string, transport http.RoundTripper) (*BlobCache, error) { 26 28 // Should come from config 27 29 accountName, ok := os.LookupEnv("AZURE_STORAGE_ACCOUNT_NAME") 28 30 if !ok { ··· 37 39 cred, err := azblob.NewSharedKeyCredential(accountName, accountKey) 38 40 if err != nil { 39 41 return nil, fmt.Errorf("failed to create shared key credential: %w", err) 42 + } 43 + if transport == nil { 44 + transport = http.DefaultTransport 40 45 } 41 46 42 47 // The service URL for blob endpoints is usually in the form: http(s)://<account>.blob.core.windows.net/ 43 - client, err := azblob.NewClientWithSharedKeyCredential(fmt.Sprintf("https://%s.blob.core.windows.net/", accountName), cred, nil) 48 + client, err := azblob.NewClientWithSharedKeyCredential(fmt.Sprintf("https://%s.blob.core.windows.net/", accountName), cred, &azblob.ClientOptions{ 49 + ClientOptions: policy.ClientOptions{ 50 + Transport: &http.Client{Transport: transport}, 51 + }, 52 + }) 44 53 if err != nil { 45 54 return nil, fmt.Errorf("failed to create blob client: %w", err) 46 55 } ··· 142 151 return EnsureCache("recipes") 143 152 } 144 153 154 + // take transport here? 145 155 func EnsureCache(container string) (ListCache, error) { 146 156 _, ok := os.LookupEnv("AZURE_STORAGE_ACCOUNT_NAME") 147 157 if ok { 148 158 slog.Info("Using Azure Blob Storage for cache", "container", container) 149 - return NewBlobCache(container) 159 + // can pas in otelhttp.NewTransport(http.DefaultTransport) but it creates a lot of noise 160 + return NewBlobCache(container, http.DefaultTransport) 150 161 } 151 162 return NewFileCache(container), nil 152 163 }
+19 -10
internal/ingredients/grading/cache.go
··· 5 5 "errors" 6 6 "fmt" 7 7 "log/slog" 8 + "sync" 8 9 9 10 "careme/internal/ai" 10 11 "careme/internal/cache" ··· 80 81 if len(missingIngredients) == 0 { 81 82 return results, nil 82 83 } 84 + slog.InfoContext(ctx, "grading non cached", "cached", len(results), "missing", len(missingIngredients)) 83 85 84 86 gradedIngredients, err := c.grader.GradeIngredients(ctx, missingIngredients) 85 - if err != nil { 86 - return nil, err 87 - } 88 - if len(gradedIngredients) != len(missingIngredients) { 89 - return nil, fmt.Errorf("ingredient grader returned %d ingredients for %d inputs", len(gradedIngredients), len(missingIngredients)) 90 - } 91 87 88 + // might get partial results back save those. 89 + var wg sync.WaitGroup 92 90 for _, gradedIngredient := range gradedIngredients { 93 91 if gradedIngredient.Grade == nil { 94 92 return nil, fmt.Errorf("ingredient grader returned nil grade for %q", ingredientLabel(gradedIngredient)) 95 93 } 96 - key := cacheKey(c.cacheVersion + "/" + ingredientHash(gradedIngredient)) 97 - if err := c.store.Save(ctx, key, &gradedIngredient); err != nil { 98 - slog.ErrorContext(ctx, "failed to cache ingredient grade", "key", key, "ingredient", ingredientLabel(gradedIngredient), "error", err) 99 - } 100 94 results = append(results, gradedIngredient) 95 + wg.Go(func() { 96 + ctx := context.WithoutCancel(ctx) 97 + key := cacheKey(c.cacheVersion + "/" + ingredientHash(gradedIngredient)) 98 + if err := c.store.Save(ctx, key, &gradedIngredient); err != nil { 99 + slog.ErrorContext(ctx, "failed to cache ingredient grade", "key", key, "ingredient", ingredientLabel(gradedIngredient), "error", err) 100 + } 101 + }) 102 + } 103 + wg.Wait() 104 + if err != nil { 105 + return nil, err 106 + } 107 + 108 + if len(gradedIngredients) != len(missingIngredients) { 109 + return nil, fmt.Errorf("ingredient grader returned %d ingredients for %d inputs", len(gradedIngredients), len(missingIngredients)) 101 110 } 102 111 103 112 return results, nil
+11 -13
internal/ingredients/grading/manager.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "net/http" 5 6 "strings" 6 7 7 8 "careme/internal/ai" ··· 35 36 } 36 37 37 38 type multiGrader struct { 38 - grader grader 39 + grader baseGrader 39 40 } 40 41 41 - func NewManager(cfg *config.Config, c cache.ListCache) grader { 42 + func (m *multiGrader) CacheVersion() string { 43 + return m.grader.CacheVersion() 44 + } 45 + 46 + func NewManager(cfg *config.Config, c cache.ListCache, httpClient *http.Client) grader { 42 47 if cfg == nil || !cfg.IngredientGrading.Enable || strings.TrimSpace(cfg.AI.APIKey) == "" { 43 48 return rubberstamp{} 44 49 } 45 - base := ai.NewIngredientGrader(cfg.AI.APIKey, cfg.IngredientGrading.Model) 46 - return &multiGrader{ 47 - grader: newCachingGrader(base, NewStore(c)), 48 - } 50 + base := ai.NewIngredientGrader(cfg.AI.APIKey, cfg.IngredientGrading.Model, httpClient) 51 + return newCachingGrader(&multiGrader{grader: base}, NewStore(c)) 49 52 } 50 53 51 54 func (m *multiGrader) GradeIngredients(ctx context.Context, ingredients []ai.InputIngredient) ([]ai.InputIngredient, error) { ··· 54 57 } 55 58 56 59 // we assume dedupe before thing come in here 57 - 58 60 batches := lo.Chunk(ingredients, ingredientGradeBatchSize) 59 - graded, err := parallelism.Flatten(batches, func(batch []ai.InputIngredient) ([]ai.InputIngredient, error) { 61 + // return partial results so we can cache them 62 + return parallelism.Flatten(batches, func(batch []ai.InputIngredient) ([]ai.InputIngredient, error) { 60 63 return m.grader.GradeIngredients(ctx, batch) 61 64 }) 62 - if err != nil { 63 - // will have cached these 64 - return nil, err 65 - } 66 - return graded, nil 67 65 } 68 66 69 67 func ingredientHash(ingredient ai.InputIngredient) string {
+1 -3
internal/ingredients/grading/manager_test.go
··· 103 103 func TestMultiGraderBatchesUniqueIngredientsInChunksOf30(t *testing.T) { 104 104 cacheStore := NewStore(cache.NewInMemoryCache()) 105 105 backend := &stubGradeBackend{} 106 - manager := &multiGrader{ 107 - grader: newCachingGrader(backend, cacheStore), 108 - } 106 + manager := newCachingGrader(&multiGrader{backend}, cacheStore) 109 107 110 108 ingredients := make([]ai.InputIngredient, 65) 111 109 for i := range ingredients {
+2 -2
internal/kroger/cfg.yaml internal/kroger/locations/cfg.yaml
··· 1 1 # yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json 2 - package: client 2 + package: locations 3 3 output: client.gen.go 4 4 generate: 5 5 models: true 6 - client: true 6 + client: true
-2869
internal/kroger/client.gen.go
··· 1 - // Package client provides primitives to interact with the openapi HTTP API. 2 - // 3 - // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT. 4 - package kroger 5 - 6 - import ( 7 - "bytes" 8 - "context" 9 - "encoding/json" 10 - "fmt" 11 - "io" 12 - "net/http" 13 - "net/url" 14 - "strings" 15 - 16 - "github.com/oapi-codegen/runtime" 17 - ) 18 - 19 - const ( 20 - OAuth2AuthorizationCodeScopes = "OAuth2AuthorizationCode.Scopes" 21 - OAuth2ClientCredsScopes = "OAuth2ClientCreds.Scopes" 22 - ) 23 - 24 - // AddToCartJSONBody defines parameters for AddToCart. 25 - type AddToCartJSONBody struct { 26 - Items *[]struct { 27 - Modality *string `json:"modality,omitempty"` 28 - Quantity *float32 `json:"quantity,omitempty"` 29 - Upc *string `json:"upc,omitempty"` 30 - } `json:"items,omitempty"` 31 - } 32 - 33 - // LocationListParams defines parameters for LocationList. 34 - type LocationListParams struct { 35 - // FilterZipCodeNear The zip code you want to use as a starting point for results. 36 - FilterZipCodeNear *string `form:"filter.zipCode.near,omitempty" json:"filter.zipCode.near,omitempty"` 37 - 38 - // FilterLatLongNear The latitude and longitude you want to use as a starting point for results. 39 - FilterLatLongNear *string `form:"filter.latLong.near,omitempty" json:"filter.latLong.near,omitempty"` 40 - 41 - // FilterLatNear The latitude you want to use as a starting point for results. 42 - FilterLatNear *string `form:"filter.lat.near,omitempty" json:"filter.lat.near,omitempty"` 43 - 44 - // FilterLonNear The longitude you want to use as a starting point for results. 45 - FilterLonNear *string `form:"filter.lon.near,omitempty" json:"filter.lon.near,omitempty"` 46 - 47 - // FilterRadiusInMiles The mile radius you want results limited to. 48 - FilterRadiusInMiles *string `form:"filter.radiusInMiles,omitempty" json:"filter.radiusInMiles,omitempty"` 49 - 50 - // FilterLimit The number of results you want returned. 51 - FilterLimit *string `form:"filter.limit,omitempty" json:"filter.limit,omitempty"` 52 - 53 - // FilterChain The chain name of the chain you want results limited to. When using this filter, only stores matching the provided chain name are returned. 54 - FilterChain *string `form:"filter.chain,omitempty" json:"filter.chain,omitempty"` 55 - 56 - // FilterDepartment The departmentId of the department you want results limited to. Lists must be comma-separated. When using this filter, only stores who have all of the departments provided are returned. 57 - FilterDepartment *string `form:"filter.department,omitempty" json:"filter.department,omitempty"` 58 - } 59 - 60 - // ProductSearchParams defines parameters for ProductSearch. 61 - type ProductSearchParams struct { 62 - // FilterTerm A search term to filter product results. As an example, you could input milk, bread, or salt. 63 - FilterTerm *string `form:"filter.term,omitempty" json:"filter.term,omitempty"` 64 - 65 - // FilterLocationId The locationId of the store you want results limited to. When using this filter, only products available at that location are returned. 66 - FilterLocationId *string `form:"filter.locationId,omitempty" json:"filter.locationId,omitempty"` 67 - 68 - // FilterProductId The productId of the products(s) you want returned. For more than one item, the list must be comma-separated. When used, all other query parameters are ignored. 69 - FilterProductId *string `form:"filter.productId,omitempty" json:"filter.productId,omitempty"` 70 - 71 - // FilterBrand The brand name of the product(s) you want returned. When using this filter, only products by that brand are returned. Brand names are case-sensitive, and lists must be pipe-separated. 72 - FilterBrand *string `form:"filter.brand,omitempty" json:"filter.brand,omitempty"` 73 - 74 - // FilterFulfillment The available fulfillment types of the product(s) you want returned. Fulfillment types are case-sensitive, and lists must be comma-separated. Must be one or more of the follow types: ais - Available In Store, csp - Curbside Pickup, dth - Delivery To Home, sth - Ship To Home 75 - FilterFulfillment *string `form:"filter.fulfillment,omitempty" json:"filter.fulfillment,omitempty"` 76 - 77 - // FilterStart The number of products you want to skip. 78 - FilterStart *string `form:"filter.start,omitempty" json:"filter.start,omitempty"` 79 - 80 - // FilterLimit The number of products you want returned. 81 - FilterLimit *string `form:"filter.limit,omitempty" json:"filter.limit,omitempty"` 82 - } 83 - 84 - // ProductDetailsParams defines parameters for ProductDetails. 85 - type ProductDetailsParams struct { 86 - // FilterLocationId The locationId of the store you want results limited to. When using this filter, only products available at that location are returned. 87 - FilterLocationId *string `form:"filter.locationId,omitempty" json:"filter.locationId,omitempty"` 88 - } 89 - 90 - // AddToCartJSONRequestBody defines body for AddToCart for application/json ContentType. 91 - type AddToCartJSONRequestBody AddToCartJSONBody 92 - 93 - // RequestEditorFn is the function signature for the RequestEditor callback function 94 - type RequestEditorFn func(ctx context.Context, req *http.Request) error 95 - 96 - // Doer performs HTTP requests. 97 - // 98 - // The standard http.Client implements this interface. 99 - type HttpRequestDoer interface { 100 - Do(req *http.Request) (*http.Response, error) 101 - } 102 - 103 - // Client which conforms to the OpenAPI3 specification for this service. 104 - type Client struct { 105 - // The endpoint of the server conforming to this interface, with scheme, 106 - // https://api.deepmap.com for example. This can contain a path relative 107 - // to the server, such as https://api.deepmap.com/dev-test, and all the 108 - // paths in the swagger spec will be appended to the server. 109 - Server string 110 - 111 - // Doer for performing requests, typically a *http.Client with any 112 - // customized settings, such as certificate chains. 113 - Client HttpRequestDoer 114 - 115 - // A list of callbacks for modifying requests which are generated before sending over 116 - // the network. 117 - RequestEditors []RequestEditorFn 118 - } 119 - 120 - // ClientOption allows setting custom parameters during construction 121 - type ClientOption func(*Client) error 122 - 123 - // Creates a new Client, with reasonable defaults 124 - func NewClient(server string, opts ...ClientOption) (*Client, error) { 125 - // create a client with sane default values 126 - client := Client{ 127 - Server: server, 128 - } 129 - // mutate client and add all optional params 130 - for _, o := range opts { 131 - if err := o(&client); err != nil { 132 - return nil, err 133 - } 134 - } 135 - // ensure the server URL always has a trailing slash 136 - if !strings.HasSuffix(client.Server, "/") { 137 - client.Server += "/" 138 - } 139 - // create httpClient, if not already present 140 - if client.Client == nil { 141 - client.Client = &http.Client{} 142 - } 143 - return &client, nil 144 - } 145 - 146 - // WithHTTPClient allows overriding the default Doer, which is 147 - // automatically created using http.Client. This is useful for tests. 148 - func WithHTTPClient(doer HttpRequestDoer) ClientOption { 149 - return func(c *Client) error { 150 - c.Client = doer 151 - return nil 152 - } 153 - } 154 - 155 - // WithRequestEditorFn allows setting up a callback function, which will be 156 - // called right before sending the request. This can be used to mutate the request. 157 - func WithRequestEditorFn(fn RequestEditorFn) ClientOption { 158 - return func(c *Client) error { 159 - c.RequestEditors = append(c.RequestEditors, fn) 160 - return nil 161 - } 162 - } 163 - 164 - // The interface specification for the client above. 165 - type ClientInterface interface { 166 - // AddToCartWithBody request with any body 167 - AddToCartWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) 168 - 169 - AddToCart(ctx context.Context, body AddToCartJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) 170 - 171 - // ChainList request 172 - ChainList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) 173 - 174 - // ChainDetails request 175 - ChainDetails(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error) 176 - 177 - // DepartmentList request 178 - DepartmentList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) 179 - 180 - // DepartmentDetails request 181 - DepartmentDetails(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) 182 - 183 - // UserProfileInformation request 184 - UserProfileInformation(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) 185 - 186 - // LocationList request 187 - LocationList(ctx context.Context, params *LocationListParams, reqEditors ...RequestEditorFn) (*http.Response, error) 188 - 189 - // LocationDetails request 190 - LocationDetails(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*http.Response, error) 191 - 192 - // ProductSearch request 193 - ProductSearch(ctx context.Context, params *ProductSearchParams, reqEditors ...RequestEditorFn) (*http.Response, error) 194 - 195 - // ProductDetails request 196 - ProductDetails(ctx context.Context, id string, params *ProductDetailsParams, reqEditors ...RequestEditorFn) (*http.Response, error) 197 - } 198 - 199 - func (c *Client) AddToCartWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { 200 - req, err := NewAddToCartRequestWithBody(c.Server, contentType, body) 201 - if err != nil { 202 - return nil, err 203 - } 204 - req = req.WithContext(ctx) 205 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 206 - return nil, err 207 - } 208 - return c.Client.Do(req) 209 - } 210 - 211 - func (c *Client) AddToCart(ctx context.Context, body AddToCartJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { 212 - req, err := NewAddToCartRequest(c.Server, body) 213 - if err != nil { 214 - return nil, err 215 - } 216 - req = req.WithContext(ctx) 217 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 218 - return nil, err 219 - } 220 - return c.Client.Do(req) 221 - } 222 - 223 - func (c *Client) ChainList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { 224 - req, err := NewChainListRequest(c.Server) 225 - if err != nil { 226 - return nil, err 227 - } 228 - req = req.WithContext(ctx) 229 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 230 - return nil, err 231 - } 232 - return c.Client.Do(req) 233 - } 234 - 235 - func (c *Client) ChainDetails(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*http.Response, error) { 236 - req, err := NewChainDetailsRequest(c.Server, name) 237 - if err != nil { 238 - return nil, err 239 - } 240 - req = req.WithContext(ctx) 241 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 242 - return nil, err 243 - } 244 - return c.Client.Do(req) 245 - } 246 - 247 - func (c *Client) DepartmentList(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { 248 - req, err := NewDepartmentListRequest(c.Server) 249 - if err != nil { 250 - return nil, err 251 - } 252 - req = req.WithContext(ctx) 253 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 254 - return nil, err 255 - } 256 - return c.Client.Do(req) 257 - } 258 - 259 - func (c *Client) DepartmentDetails(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { 260 - req, err := NewDepartmentDetailsRequest(c.Server, id) 261 - if err != nil { 262 - return nil, err 263 - } 264 - req = req.WithContext(ctx) 265 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 266 - return nil, err 267 - } 268 - return c.Client.Do(req) 269 - } 270 - 271 - func (c *Client) UserProfileInformation(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { 272 - req, err := NewUserProfileInformationRequest(c.Server) 273 - if err != nil { 274 - return nil, err 275 - } 276 - req = req.WithContext(ctx) 277 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 278 - return nil, err 279 - } 280 - return c.Client.Do(req) 281 - } 282 - 283 - func (c *Client) LocationList(ctx context.Context, params *LocationListParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 284 - req, err := NewLocationListRequest(c.Server, params) 285 - if err != nil { 286 - return nil, err 287 - } 288 - req = req.WithContext(ctx) 289 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 290 - return nil, err 291 - } 292 - return c.Client.Do(req) 293 - } 294 - 295 - func (c *Client) LocationDetails(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*http.Response, error) { 296 - req, err := NewLocationDetailsRequest(c.Server, locationId) 297 - if err != nil { 298 - return nil, err 299 - } 300 - req = req.WithContext(ctx) 301 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 302 - return nil, err 303 - } 304 - return c.Client.Do(req) 305 - } 306 - 307 - func (c *Client) ProductSearch(ctx context.Context, params *ProductSearchParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 308 - req, err := NewProductSearchRequest(c.Server, params) 309 - if err != nil { 310 - return nil, err 311 - } 312 - req = req.WithContext(ctx) 313 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 314 - return nil, err 315 - } 316 - return c.Client.Do(req) 317 - } 318 - 319 - func (c *Client) ProductDetails(ctx context.Context, id string, params *ProductDetailsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 320 - req, err := NewProductDetailsRequest(c.Server, id, params) 321 - if err != nil { 322 - return nil, err 323 - } 324 - req = req.WithContext(ctx) 325 - if err := c.applyEditors(ctx, req, reqEditors); err != nil { 326 - return nil, err 327 - } 328 - return c.Client.Do(req) 329 - } 330 - 331 - // NewAddToCartRequest calls the generic AddToCart builder with application/json body 332 - func NewAddToCartRequest(server string, body AddToCartJSONRequestBody) (*http.Request, error) { 333 - var bodyReader io.Reader 334 - buf, err := json.Marshal(body) 335 - if err != nil { 336 - return nil, err 337 - } 338 - bodyReader = bytes.NewReader(buf) 339 - return NewAddToCartRequestWithBody(server, "application/json", bodyReader) 340 - } 341 - 342 - // NewAddToCartRequestWithBody generates requests for AddToCart with any type of body 343 - func NewAddToCartRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { 344 - var err error 345 - 346 - serverURL, err := url.Parse(server) 347 - if err != nil { 348 - return nil, err 349 - } 350 - 351 - operationPath := fmt.Sprintf("/cart/add") 352 - if operationPath[0] == '/' { 353 - operationPath = "." + operationPath 354 - } 355 - 356 - queryURL, err := serverURL.Parse(operationPath) 357 - if err != nil { 358 - return nil, err 359 - } 360 - 361 - req, err := http.NewRequest("PUT", queryURL.String(), body) 362 - if err != nil { 363 - return nil, err 364 - } 365 - 366 - req.Header.Add("Content-Type", contentType) 367 - 368 - return req, nil 369 - } 370 - 371 - // NewChainListRequest generates requests for ChainList 372 - func NewChainListRequest(server string) (*http.Request, error) { 373 - var err error 374 - 375 - serverURL, err := url.Parse(server) 376 - if err != nil { 377 - return nil, err 378 - } 379 - 380 - operationPath := fmt.Sprintf("/chains") 381 - if operationPath[0] == '/' { 382 - operationPath = "." + operationPath 383 - } 384 - 385 - queryURL, err := serverURL.Parse(operationPath) 386 - if err != nil { 387 - return nil, err 388 - } 389 - 390 - req, err := http.NewRequest("GET", queryURL.String(), nil) 391 - if err != nil { 392 - return nil, err 393 - } 394 - 395 - return req, nil 396 - } 397 - 398 - // NewChainDetailsRequest generates requests for ChainDetails 399 - func NewChainDetailsRequest(server string, name string) (*http.Request, error) { 400 - var err error 401 - 402 - var pathParam0 string 403 - 404 - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "name", runtime.ParamLocationPath, name) 405 - if err != nil { 406 - return nil, err 407 - } 408 - 409 - serverURL, err := url.Parse(server) 410 - if err != nil { 411 - return nil, err 412 - } 413 - 414 - operationPath := fmt.Sprintf("/chains/%s", pathParam0) 415 - if operationPath[0] == '/' { 416 - operationPath = "." + operationPath 417 - } 418 - 419 - queryURL, err := serverURL.Parse(operationPath) 420 - if err != nil { 421 - return nil, err 422 - } 423 - 424 - req, err := http.NewRequest("GET", queryURL.String(), nil) 425 - if err != nil { 426 - return nil, err 427 - } 428 - 429 - return req, nil 430 - } 431 - 432 - // NewDepartmentListRequest generates requests for DepartmentList 433 - func NewDepartmentListRequest(server string) (*http.Request, error) { 434 - var err error 435 - 436 - serverURL, err := url.Parse(server) 437 - if err != nil { 438 - return nil, err 439 - } 440 - 441 - operationPath := fmt.Sprintf("/departments") 442 - if operationPath[0] == '/' { 443 - operationPath = "." + operationPath 444 - } 445 - 446 - queryURL, err := serverURL.Parse(operationPath) 447 - if err != nil { 448 - return nil, err 449 - } 450 - 451 - req, err := http.NewRequest("GET", queryURL.String(), nil) 452 - if err != nil { 453 - return nil, err 454 - } 455 - 456 - return req, nil 457 - } 458 - 459 - // NewDepartmentDetailsRequest generates requests for DepartmentDetails 460 - func NewDepartmentDetailsRequest(server string, id string) (*http.Request, error) { 461 - var err error 462 - 463 - var pathParam0 string 464 - 465 - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) 466 - if err != nil { 467 - return nil, err 468 - } 469 - 470 - serverURL, err := url.Parse(server) 471 - if err != nil { 472 - return nil, err 473 - } 474 - 475 - operationPath := fmt.Sprintf("/departments/%s", pathParam0) 476 - if operationPath[0] == '/' { 477 - operationPath = "." + operationPath 478 - } 479 - 480 - queryURL, err := serverURL.Parse(operationPath) 481 - if err != nil { 482 - return nil, err 483 - } 484 - 485 - req, err := http.NewRequest("GET", queryURL.String(), nil) 486 - if err != nil { 487 - return nil, err 488 - } 489 - 490 - return req, nil 491 - } 492 - 493 - // NewUserProfileInformationRequest generates requests for UserProfileInformation 494 - func NewUserProfileInformationRequest(server string) (*http.Request, error) { 495 - var err error 496 - 497 - serverURL, err := url.Parse(server) 498 - if err != nil { 499 - return nil, err 500 - } 501 - 502 - operationPath := fmt.Sprintf("/identity/profile") 503 - if operationPath[0] == '/' { 504 - operationPath = "." + operationPath 505 - } 506 - 507 - queryURL, err := serverURL.Parse(operationPath) 508 - if err != nil { 509 - return nil, err 510 - } 511 - 512 - req, err := http.NewRequest("GET", queryURL.String(), nil) 513 - if err != nil { 514 - return nil, err 515 - } 516 - 517 - return req, nil 518 - } 519 - 520 - // NewLocationListRequest generates requests for LocationList 521 - func NewLocationListRequest(server string, params *LocationListParams) (*http.Request, error) { 522 - var err error 523 - 524 - serverURL, err := url.Parse(server) 525 - if err != nil { 526 - return nil, err 527 - } 528 - 529 - operationPath := fmt.Sprintf("/locations") 530 - if operationPath[0] == '/' { 531 - operationPath = "." + operationPath 532 - } 533 - 534 - queryURL, err := serverURL.Parse(operationPath) 535 - if err != nil { 536 - return nil, err 537 - } 538 - 539 - if params != nil { 540 - queryValues := queryURL.Query() 541 - 542 - if params.FilterZipCodeNear != nil { 543 - 544 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.zipCode.near", runtime.ParamLocationQuery, *params.FilterZipCodeNear); err != nil { 545 - return nil, err 546 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 547 - return nil, err 548 - } else { 549 - for k, v := range parsed { 550 - for _, v2 := range v { 551 - queryValues.Add(k, v2) 552 - } 553 - } 554 - } 555 - 556 - } 557 - 558 - if params.FilterLatLongNear != nil { 559 - 560 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.latLong.near", runtime.ParamLocationQuery, *params.FilterLatLongNear); err != nil { 561 - return nil, err 562 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 563 - return nil, err 564 - } else { 565 - for k, v := range parsed { 566 - for _, v2 := range v { 567 - queryValues.Add(k, v2) 568 - } 569 - } 570 - } 571 - 572 - } 573 - 574 - if params.FilterLatNear != nil { 575 - 576 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.lat.near", runtime.ParamLocationQuery, *params.FilterLatNear); err != nil { 577 - return nil, err 578 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 579 - return nil, err 580 - } else { 581 - for k, v := range parsed { 582 - for _, v2 := range v { 583 - queryValues.Add(k, v2) 584 - } 585 - } 586 - } 587 - 588 - } 589 - 590 - if params.FilterLonNear != nil { 591 - 592 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.lon.near", runtime.ParamLocationQuery, *params.FilterLonNear); err != nil { 593 - return nil, err 594 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 595 - return nil, err 596 - } else { 597 - for k, v := range parsed { 598 - for _, v2 := range v { 599 - queryValues.Add(k, v2) 600 - } 601 - } 602 - } 603 - 604 - } 605 - 606 - if params.FilterRadiusInMiles != nil { 607 - 608 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.radiusInMiles", runtime.ParamLocationQuery, *params.FilterRadiusInMiles); err != nil { 609 - return nil, err 610 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 611 - return nil, err 612 - } else { 613 - for k, v := range parsed { 614 - for _, v2 := range v { 615 - queryValues.Add(k, v2) 616 - } 617 - } 618 - } 619 - 620 - } 621 - 622 - if params.FilterLimit != nil { 623 - 624 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.limit", runtime.ParamLocationQuery, *params.FilterLimit); err != nil { 625 - return nil, err 626 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 627 - return nil, err 628 - } else { 629 - for k, v := range parsed { 630 - for _, v2 := range v { 631 - queryValues.Add(k, v2) 632 - } 633 - } 634 - } 635 - 636 - } 637 - 638 - if params.FilterChain != nil { 639 - 640 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.chain", runtime.ParamLocationQuery, *params.FilterChain); err != nil { 641 - return nil, err 642 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 643 - return nil, err 644 - } else { 645 - for k, v := range parsed { 646 - for _, v2 := range v { 647 - queryValues.Add(k, v2) 648 - } 649 - } 650 - } 651 - 652 - } 653 - 654 - if params.FilterDepartment != nil { 655 - 656 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.department", runtime.ParamLocationQuery, *params.FilterDepartment); err != nil { 657 - return nil, err 658 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 659 - return nil, err 660 - } else { 661 - for k, v := range parsed { 662 - for _, v2 := range v { 663 - queryValues.Add(k, v2) 664 - } 665 - } 666 - } 667 - 668 - } 669 - 670 - queryURL.RawQuery = queryValues.Encode() 671 - } 672 - 673 - req, err := http.NewRequest("GET", queryURL.String(), nil) 674 - if err != nil { 675 - return nil, err 676 - } 677 - 678 - return req, nil 679 - } 680 - 681 - // NewLocationDetailsRequest generates requests for LocationDetails 682 - func NewLocationDetailsRequest(server string, locationId string) (*http.Request, error) { 683 - var err error 684 - 685 - var pathParam0 string 686 - 687 - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "locationId", runtime.ParamLocationPath, locationId) 688 - if err != nil { 689 - return nil, err 690 - } 691 - 692 - serverURL, err := url.Parse(server) 693 - if err != nil { 694 - return nil, err 695 - } 696 - 697 - operationPath := fmt.Sprintf("/locations/%s", pathParam0) 698 - if operationPath[0] == '/' { 699 - operationPath = "." + operationPath 700 - } 701 - 702 - queryURL, err := serverURL.Parse(operationPath) 703 - if err != nil { 704 - return nil, err 705 - } 706 - 707 - req, err := http.NewRequest("GET", queryURL.String(), nil) 708 - if err != nil { 709 - return nil, err 710 - } 711 - 712 - return req, nil 713 - } 714 - 715 - // NewProductSearchRequest generates requests for ProductSearch 716 - func NewProductSearchRequest(server string, params *ProductSearchParams) (*http.Request, error) { 717 - var err error 718 - 719 - serverURL, err := url.Parse(server) 720 - if err != nil { 721 - return nil, err 722 - } 723 - 724 - operationPath := fmt.Sprintf("/products") 725 - if operationPath[0] == '/' { 726 - operationPath = "." + operationPath 727 - } 728 - 729 - queryURL, err := serverURL.Parse(operationPath) 730 - if err != nil { 731 - return nil, err 732 - } 733 - 734 - if params != nil { 735 - queryValues := queryURL.Query() 736 - 737 - if params.FilterTerm != nil { 738 - 739 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.term", runtime.ParamLocationQuery, *params.FilterTerm); err != nil { 740 - return nil, err 741 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 742 - return nil, err 743 - } else { 744 - for k, v := range parsed { 745 - for _, v2 := range v { 746 - queryValues.Add(k, v2) 747 - } 748 - } 749 - } 750 - 751 - } 752 - 753 - if params.FilterLocationId != nil { 754 - 755 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.locationId", runtime.ParamLocationQuery, *params.FilterLocationId); err != nil { 756 - return nil, err 757 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 758 - return nil, err 759 - } else { 760 - for k, v := range parsed { 761 - for _, v2 := range v { 762 - queryValues.Add(k, v2) 763 - } 764 - } 765 - } 766 - 767 - } 768 - 769 - if params.FilterProductId != nil { 770 - 771 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.productId", runtime.ParamLocationQuery, *params.FilterProductId); err != nil { 772 - return nil, err 773 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 774 - return nil, err 775 - } else { 776 - for k, v := range parsed { 777 - for _, v2 := range v { 778 - queryValues.Add(k, v2) 779 - } 780 - } 781 - } 782 - 783 - } 784 - 785 - if params.FilterBrand != nil { 786 - 787 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.brand", runtime.ParamLocationQuery, *params.FilterBrand); err != nil { 788 - return nil, err 789 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 790 - return nil, err 791 - } else { 792 - for k, v := range parsed { 793 - for _, v2 := range v { 794 - queryValues.Add(k, v2) 795 - } 796 - } 797 - } 798 - 799 - } 800 - 801 - if params.FilterFulfillment != nil { 802 - 803 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.fulfillment", runtime.ParamLocationQuery, *params.FilterFulfillment); err != nil { 804 - return nil, err 805 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 806 - return nil, err 807 - } else { 808 - for k, v := range parsed { 809 - for _, v2 := range v { 810 - queryValues.Add(k, v2) 811 - } 812 - } 813 - } 814 - 815 - } 816 - 817 - if params.FilterStart != nil { 818 - 819 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.start", runtime.ParamLocationQuery, *params.FilterStart); err != nil { 820 - return nil, err 821 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 822 - return nil, err 823 - } else { 824 - for k, v := range parsed { 825 - for _, v2 := range v { 826 - queryValues.Add(k, v2) 827 - } 828 - } 829 - } 830 - 831 - } 832 - 833 - if params.FilterLimit != nil { 834 - 835 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.limit", runtime.ParamLocationQuery, *params.FilterLimit); err != nil { 836 - return nil, err 837 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 838 - return nil, err 839 - } else { 840 - for k, v := range parsed { 841 - for _, v2 := range v { 842 - queryValues.Add(k, v2) 843 - } 844 - } 845 - } 846 - 847 - } 848 - 849 - queryURL.RawQuery = queryValues.Encode() 850 - } 851 - 852 - req, err := http.NewRequest("GET", queryURL.String(), nil) 853 - if err != nil { 854 - return nil, err 855 - } 856 - 857 - return req, nil 858 - } 859 - 860 - // NewProductDetailsRequest generates requests for ProductDetails 861 - func NewProductDetailsRequest(server string, id string, params *ProductDetailsParams) (*http.Request, error) { 862 - var err error 863 - 864 - var pathParam0 string 865 - 866 - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) 867 - if err != nil { 868 - return nil, err 869 - } 870 - 871 - serverURL, err := url.Parse(server) 872 - if err != nil { 873 - return nil, err 874 - } 875 - 876 - operationPath := fmt.Sprintf("/products/%s", pathParam0) 877 - if operationPath[0] == '/' { 878 - operationPath = "." + operationPath 879 - } 880 - 881 - queryURL, err := serverURL.Parse(operationPath) 882 - if err != nil { 883 - return nil, err 884 - } 885 - 886 - if params != nil { 887 - queryValues := queryURL.Query() 888 - 889 - if params.FilterLocationId != nil { 890 - 891 - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "filter.locationId", runtime.ParamLocationQuery, *params.FilterLocationId); err != nil { 892 - return nil, err 893 - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 894 - return nil, err 895 - } else { 896 - for k, v := range parsed { 897 - for _, v2 := range v { 898 - queryValues.Add(k, v2) 899 - } 900 - } 901 - } 902 - 903 - } 904 - 905 - queryURL.RawQuery = queryValues.Encode() 906 - } 907 - 908 - req, err := http.NewRequest("GET", queryURL.String(), nil) 909 - if err != nil { 910 - return nil, err 911 - } 912 - 913 - return req, nil 914 - } 915 - 916 - func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { 917 - for _, r := range c.RequestEditors { 918 - if err := r(ctx, req); err != nil { 919 - return err 920 - } 921 - } 922 - for _, r := range additionalEditors { 923 - if err := r(ctx, req); err != nil { 924 - return err 925 - } 926 - } 927 - return nil 928 - } 929 - 930 - // ClientWithResponses builds on ClientInterface to offer response payloads 931 - type ClientWithResponses struct { 932 - ClientInterface 933 - } 934 - 935 - // NewClientWithResponses creates a new ClientWithResponses, which wraps 936 - // Client with return type handling 937 - func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { 938 - client, err := NewClient(server, opts...) 939 - if err != nil { 940 - return nil, err 941 - } 942 - return &ClientWithResponses{client}, nil 943 - } 944 - 945 - // WithBaseURL overrides the baseURL. 946 - func WithBaseURL(baseURL string) ClientOption { 947 - return func(c *Client) error { 948 - newBaseURL, err := url.Parse(baseURL) 949 - if err != nil { 950 - return err 951 - } 952 - c.Server = newBaseURL.String() 953 - return nil 954 - } 955 - } 956 - 957 - // ClientWithResponsesInterface is the interface specification for the client with responses above. 958 - type ClientWithResponsesInterface interface { 959 - // AddToCartWithBodyWithResponse request with any body 960 - AddToCartWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddToCartResponse, error) 961 - 962 - AddToCartWithResponse(ctx context.Context, body AddToCartJSONRequestBody, reqEditors ...RequestEditorFn) (*AddToCartResponse, error) 963 - 964 - // ChainListWithResponse request 965 - ChainListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ChainListResponse, error) 966 - 967 - // ChainDetailsWithResponse request 968 - ChainDetailsWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*ChainDetailsResponse, error) 969 - 970 - // DepartmentListWithResponse request 971 - DepartmentListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DepartmentListResponse, error) 972 - 973 - // DepartmentDetailsWithResponse request 974 - DepartmentDetailsWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DepartmentDetailsResponse, error) 975 - 976 - // UserProfileInformationWithResponse request 977 - UserProfileInformationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserProfileInformationResponse, error) 978 - 979 - // LocationListWithResponse request 980 - LocationListWithResponse(ctx context.Context, params *LocationListParams, reqEditors ...RequestEditorFn) (*LocationListResponse, error) 981 - 982 - // LocationDetailsWithResponse request 983 - LocationDetailsWithResponse(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*LocationDetailsResponse, error) 984 - 985 - // ProductSearchWithResponse request 986 - ProductSearchWithResponse(ctx context.Context, params *ProductSearchParams, reqEditors ...RequestEditorFn) (*ProductSearchResponse, error) 987 - 988 - // ProductDetailsWithResponse request 989 - ProductDetailsWithResponse(ctx context.Context, id string, params *ProductDetailsParams, reqEditors ...RequestEditorFn) (*ProductDetailsResponse, error) 990 - } 991 - 992 - type AddToCartResponse struct { 993 - Body []byte 994 - HTTPResponse *http.Response 995 - JSON400 *struct { 996 - Errors *string `json:"errors,omitempty"` 997 - } 998 - JSON401 *struct { 999 - Errors *struct { 1000 - Error *string `json:"error,omitempty"` 1001 - ErrorDescription *string `json:"error_description,omitempty"` 1002 - } `json:"errors,omitempty"` 1003 - } 1004 - JSON500 *struct { 1005 - Errors *struct { 1006 - Code *string `json:"code,omitempty"` 1007 - Reason *string `json:"reason,omitempty"` 1008 - Timestamp *float32 `json:"timestamp,omitempty"` 1009 - } `json:"errors,omitempty"` 1010 - } 1011 - } 1012 - 1013 - // Status returns HTTPResponse.Status 1014 - func (r AddToCartResponse) Status() string { 1015 - if r.HTTPResponse != nil { 1016 - return r.HTTPResponse.Status 1017 - } 1018 - return http.StatusText(0) 1019 - } 1020 - 1021 - // StatusCode returns HTTPResponse.StatusCode 1022 - func (r AddToCartResponse) StatusCode() int { 1023 - if r.HTTPResponse != nil { 1024 - return r.HTTPResponse.StatusCode 1025 - } 1026 - return 0 1027 - } 1028 - 1029 - type ChainListResponse struct { 1030 - Body []byte 1031 - HTTPResponse *http.Response 1032 - JSON200 *struct { 1033 - Data *[]struct { 1034 - DivisionNumbers *[]string `json:"divisionNumbers,omitempty"` 1035 - Name *string `json:"name,omitempty"` 1036 - } `json:"data,omitempty"` 1037 - Meta *struct { 1038 - Pagination *struct { 1039 - Limit *float32 `json:"limit,omitempty"` 1040 - Start *float32 `json:"start,omitempty"` 1041 - Total *float32 `json:"total,omitempty"` 1042 - } `json:"pagination,omitempty"` 1043 - Warnings *[]string `json:"warnings,omitempty"` 1044 - } `json:"meta,omitempty"` 1045 - } 1046 - JSON400 *struct { 1047 - Errors *struct { 1048 - Code *string `json:"code,omitempty"` 1049 - Reason *string `json:"reason,omitempty"` 1050 - Timestamp *float32 `json:"timestamp,omitempty"` 1051 - } `json:"errors,omitempty"` 1052 - } 1053 - JSON401 *struct { 1054 - Errors *struct { 1055 - Error *string `json:"error,omitempty"` 1056 - ErrorDescription *string `json:"error_description,omitempty"` 1057 - } `json:"errors,omitempty"` 1058 - } 1059 - JSON500 *struct { 1060 - Errors *struct { 1061 - Code *string `json:"code,omitempty"` 1062 - Reason *string `json:"reason,omitempty"` 1063 - Timestamp *float32 `json:"timestamp,omitempty"` 1064 - } `json:"errors,omitempty"` 1065 - } 1066 - } 1067 - 1068 - // Status returns HTTPResponse.Status 1069 - func (r ChainListResponse) Status() string { 1070 - if r.HTTPResponse != nil { 1071 - return r.HTTPResponse.Status 1072 - } 1073 - return http.StatusText(0) 1074 - } 1075 - 1076 - // StatusCode returns HTTPResponse.StatusCode 1077 - func (r ChainListResponse) StatusCode() int { 1078 - if r.HTTPResponse != nil { 1079 - return r.HTTPResponse.StatusCode 1080 - } 1081 - return 0 1082 - } 1083 - 1084 - type ChainDetailsResponse struct { 1085 - Body []byte 1086 - HTTPResponse *http.Response 1087 - JSON200 *struct { 1088 - Data *struct { 1089 - DivisionNumbers *[]string `json:"divisionNumbers,omitempty"` 1090 - Name *string `json:"name,omitempty"` 1091 - } `json:"data,omitempty"` 1092 - Meta *map[string]interface{} `json:"meta,omitempty"` 1093 - } 1094 - JSON401 *struct { 1095 - Errors *struct { 1096 - Error *string `json:"error,omitempty"` 1097 - ErrorDescription *string `json:"error_description,omitempty"` 1098 - } `json:"errors,omitempty"` 1099 - } 1100 - JSON500 *struct { 1101 - Errors *struct { 1102 - Code *string `json:"code,omitempty"` 1103 - Reason *string `json:"reason,omitempty"` 1104 - Timestamp *float32 `json:"timestamp,omitempty"` 1105 - } `json:"errors,omitempty"` 1106 - } 1107 - } 1108 - 1109 - // Status returns HTTPResponse.Status 1110 - func (r ChainDetailsResponse) Status() string { 1111 - if r.HTTPResponse != nil { 1112 - return r.HTTPResponse.Status 1113 - } 1114 - return http.StatusText(0) 1115 - } 1116 - 1117 - // StatusCode returns HTTPResponse.StatusCode 1118 - func (r ChainDetailsResponse) StatusCode() int { 1119 - if r.HTTPResponse != nil { 1120 - return r.HTTPResponse.StatusCode 1121 - } 1122 - return 0 1123 - } 1124 - 1125 - type DepartmentListResponse struct { 1126 - Body []byte 1127 - HTTPResponse *http.Response 1128 - JSON200 *struct { 1129 - Data *[]struct { 1130 - DepartmentId *string `json:"departmentId,omitempty"` 1131 - Name *string `json:"name,omitempty"` 1132 - } `json:"data,omitempty"` 1133 - Meta *struct { 1134 - Pagination *struct { 1135 - Limit *float32 `json:"limit,omitempty"` 1136 - Start *float32 `json:"start,omitempty"` 1137 - Total *float32 `json:"total,omitempty"` 1138 - } `json:"pagination,omitempty"` 1139 - Warnings *[]string `json:"warnings,omitempty"` 1140 - } `json:"meta,omitempty"` 1141 - } 1142 - JSON401 *struct { 1143 - Errors *struct { 1144 - Error *string `json:"error,omitempty"` 1145 - ErrorDescription *string `json:"error_description,omitempty"` 1146 - } `json:"errors,omitempty"` 1147 - } 1148 - JSON500 *struct { 1149 - Errors *struct { 1150 - Code *string `json:"code,omitempty"` 1151 - Reason *string `json:"reason,omitempty"` 1152 - Timestamp *float32 `json:"timestamp,omitempty"` 1153 - } `json:"errors,omitempty"` 1154 - } 1155 - } 1156 - 1157 - // Status returns HTTPResponse.Status 1158 - func (r DepartmentListResponse) Status() string { 1159 - if r.HTTPResponse != nil { 1160 - return r.HTTPResponse.Status 1161 - } 1162 - return http.StatusText(0) 1163 - } 1164 - 1165 - // StatusCode returns HTTPResponse.StatusCode 1166 - func (r DepartmentListResponse) StatusCode() int { 1167 - if r.HTTPResponse != nil { 1168 - return r.HTTPResponse.StatusCode 1169 - } 1170 - return 0 1171 - } 1172 - 1173 - type DepartmentDetailsResponse struct { 1174 - Body []byte 1175 - HTTPResponse *http.Response 1176 - JSON200 *struct { 1177 - Data *struct { 1178 - DepartmentId *string `json:"departmentId,omitempty"` 1179 - Name *string `json:"name,omitempty"` 1180 - } `json:"data,omitempty"` 1181 - Meta *map[string]interface{} `json:"meta,omitempty"` 1182 - } 1183 - JSON400 *struct { 1184 - Errors *struct { 1185 - Code *string `json:"code,omitempty"` 1186 - Reason *string `json:"reason,omitempty"` 1187 - Timestamp *float32 `json:"timestamp,omitempty"` 1188 - } `json:"errors,omitempty"` 1189 - } 1190 - JSON401 *struct { 1191 - Errors *struct { 1192 - Error *string `json:"error,omitempty"` 1193 - ErrorDescription *string `json:"error_description,omitempty"` 1194 - } `json:"errors,omitempty"` 1195 - } 1196 - JSON500 *struct { 1197 - Errors *struct { 1198 - Code *string `json:"code,omitempty"` 1199 - Reason *string `json:"reason,omitempty"` 1200 - Timestamp *float32 `json:"timestamp,omitempty"` 1201 - } `json:"errors,omitempty"` 1202 - } 1203 - } 1204 - 1205 - // Status returns HTTPResponse.Status 1206 - func (r DepartmentDetailsResponse) Status() string { 1207 - if r.HTTPResponse != nil { 1208 - return r.HTTPResponse.Status 1209 - } 1210 - return http.StatusText(0) 1211 - } 1212 - 1213 - // StatusCode returns HTTPResponse.StatusCode 1214 - func (r DepartmentDetailsResponse) StatusCode() int { 1215 - if r.HTTPResponse != nil { 1216 - return r.HTTPResponse.StatusCode 1217 - } 1218 - return 0 1219 - } 1220 - 1221 - type UserProfileInformationResponse struct { 1222 - Body []byte 1223 - HTTPResponse *http.Response 1224 - JSON200 *struct { 1225 - Data *struct { 1226 - Id *map[string]interface{} `json:"id,omitempty"` 1227 - } `json:"data,omitempty"` 1228 - Meta *struct { 1229 - Pagination *struct { 1230 - Limit *float32 `json:"limit,omitempty"` 1231 - Start *float32 `json:"start,omitempty"` 1232 - Total *float32 `json:"total,omitempty"` 1233 - } `json:"pagination,omitempty"` 1234 - Warnings *[]string `json:"warnings,omitempty"` 1235 - } `json:"meta,omitempty"` 1236 - } 1237 - JSON401 *struct { 1238 - Errors *struct { 1239 - Error *string `json:"error,omitempty"` 1240 - ErrorDescription *string `json:"error_description,omitempty"` 1241 - } `json:"errors,omitempty"` 1242 - } 1243 - JSON403 *struct { 1244 - Errors *struct { 1245 - Code *string `json:"code,omitempty"` 1246 - Reason *string `json:"reason,omitempty"` 1247 - Timestamp *float32 `json:"timestamp,omitempty"` 1248 - } `json:"errors,omitempty"` 1249 - } 1250 - JSON500 *struct { 1251 - Errors *struct { 1252 - Code *string `json:"code,omitempty"` 1253 - Reason *string `json:"reason,omitempty"` 1254 - Timestamp *float32 `json:"timestamp,omitempty"` 1255 - } `json:"errors,omitempty"` 1256 - } 1257 - } 1258 - 1259 - // Status returns HTTPResponse.Status 1260 - func (r UserProfileInformationResponse) Status() string { 1261 - if r.HTTPResponse != nil { 1262 - return r.HTTPResponse.Status 1263 - } 1264 - return http.StatusText(0) 1265 - } 1266 - 1267 - // StatusCode returns HTTPResponse.StatusCode 1268 - func (r UserProfileInformationResponse) StatusCode() int { 1269 - if r.HTTPResponse != nil { 1270 - return r.HTTPResponse.StatusCode 1271 - } 1272 - return 0 1273 - } 1274 - 1275 - type LocationListResponse struct { 1276 - Body []byte 1277 - HTTPResponse *http.Response 1278 - JSON200 *struct { 1279 - Data *[]struct { 1280 - Address *struct { 1281 - AddressLine1 *string `json:"addressLine1,omitempty"` 1282 - AddressLine2 *string `json:"addressLine2,omitempty"` 1283 - City *string `json:"city,omitempty"` 1284 - County *string `json:"county,omitempty"` 1285 - State *string `json:"state,omitempty"` 1286 - ZipCode *string `json:"zipCode,omitempty"` 1287 - } `json:"address,omitempty"` 1288 - Chain *string `json:"chain,omitempty"` 1289 - Departments *[]struct { 1290 - DepartmentId *string `json:"departmentId,omitempty"` 1291 - Hours *struct { 1292 - Open24 *bool `json:"Open24,omitempty"` 1293 - Friday *struct { 1294 - Close *string `json:"close,omitempty"` 1295 - Open *string `json:"open,omitempty"` 1296 - Open24 *bool `json:"open24,omitempty"` 1297 - } `json:"friday,omitempty"` 1298 - Monday *struct { 1299 - Close *string `json:"close,omitempty"` 1300 - Open *string `json:"open,omitempty"` 1301 - Open24 *bool `json:"open24,omitempty"` 1302 - } `json:"monday,omitempty"` 1303 - Saturday *struct { 1304 - Close *string `json:"close,omitempty"` 1305 - Open *string `json:"open,omitempty"` 1306 - Open24 *bool `json:"open24,omitempty"` 1307 - } `json:"saturday,omitempty"` 1308 - Sunday *struct { 1309 - Close *string `json:"close,omitempty"` 1310 - Open *string `json:"open,omitempty"` 1311 - Open24 *bool `json:"open24,omitempty"` 1312 - } `json:"sunday,omitempty"` 1313 - Thursday *struct { 1314 - Close *string `json:"close,omitempty"` 1315 - Open *string `json:"open,omitempty"` 1316 - Open24 *bool `json:"open24,omitempty"` 1317 - } `json:"thursday,omitempty"` 1318 - Tuesday *struct { 1319 - Close *string `json:"close,omitempty"` 1320 - Open *string `json:"open,omitempty"` 1321 - Open24 *bool `json:"open24,omitempty"` 1322 - } `json:"tuesday,omitempty"` 1323 - Wednesday *struct { 1324 - Close *string `json:"close,omitempty"` 1325 - Open *string `json:"open,omitempty"` 1326 - Open24 *bool `json:"open24,omitempty"` 1327 - } `json:"wednesday,omitempty"` 1328 - } `json:"hours,omitempty"` 1329 - Name *string `json:"name,omitempty"` 1330 - Phone *string `json:"phone,omitempty"` 1331 - } `json:"departments,omitempty"` 1332 - DivisionNumber *string `json:"divisionNumber,omitempty"` 1333 - Geolocation *struct { 1334 - LatLng *string `json:"latLng,omitempty"` 1335 - Latitude *float32 `json:"latitude,omitempty"` 1336 - Longitude *float32 `json:"longitude,omitempty"` 1337 - } `json:"geolocation,omitempty"` 1338 - Hours *struct { 1339 - Open24 *bool `json:"Open24,omitempty"` 1340 - Friday *struct { 1341 - Close *string `json:"close,omitempty"` 1342 - Open *string `json:"open,omitempty"` 1343 - Open24 *bool `json:"open24,omitempty"` 1344 - } `json:"friday,omitempty"` 1345 - GmtOffset *string `json:"gmtOffset,omitempty"` 1346 - Monday *struct { 1347 - Close *string `json:"close,omitempty"` 1348 - Open *string `json:"open,omitempty"` 1349 - Open24 *bool `json:"open24,omitempty"` 1350 - } `json:"monday,omitempty"` 1351 - Saturday *struct { 1352 - Close *string `json:"close,omitempty"` 1353 - Open *string `json:"open,omitempty"` 1354 - Open24 *bool `json:"open24,omitempty"` 1355 - } `json:"saturday,omitempty"` 1356 - Sunday *struct { 1357 - Close *string `json:"close,omitempty"` 1358 - Open *string `json:"open,omitempty"` 1359 - Open24 *bool `json:"open24,omitempty"` 1360 - } `json:"sunday,omitempty"` 1361 - Thursday *struct { 1362 - Close *string `json:"close,omitempty"` 1363 - Open *string `json:"open,omitempty"` 1364 - Open24 *bool `json:"open24,omitempty"` 1365 - } `json:"thursday,omitempty"` 1366 - Timezone *string `json:"timezone,omitempty"` 1367 - Tuesday *struct { 1368 - Close *string `json:"close,omitempty"` 1369 - Open *string `json:"open,omitempty"` 1370 - Open24 *bool `json:"open24,omitempty"` 1371 - } `json:"tuesday,omitempty"` 1372 - Wednesday *struct { 1373 - Close *string `json:"close,omitempty"` 1374 - Open *string `json:"open,omitempty"` 1375 - Open24 *bool `json:"open24,omitempty"` 1376 - } `json:"wednesday,omitempty"` 1377 - } `json:"hours,omitempty"` 1378 - LocationId *string `json:"locationId,omitempty"` 1379 - Name *string `json:"name,omitempty"` 1380 - Phone *string `json:"phone,omitempty"` 1381 - StoreNumber *string `json:"storeNumber,omitempty"` 1382 - } `json:"data,omitempty"` 1383 - Meta *struct { 1384 - Pagination *struct { 1385 - Limit *float32 `json:"limit,omitempty"` 1386 - Start *float32 `json:"start,omitempty"` 1387 - Total *float32 `json:"total,omitempty"` 1388 - } `json:"pagination,omitempty"` 1389 - Warnings *[]string `json:"warnings,omitempty"` 1390 - } `json:"meta,omitempty"` 1391 - } 1392 - JSON400 *struct { 1393 - Errors *struct { 1394 - Code *string `json:"code,omitempty"` 1395 - Reason *string `json:"reason,omitempty"` 1396 - Timestamp *float32 `json:"timestamp,omitempty"` 1397 - } `json:"errors,omitempty"` 1398 - } 1399 - JSON401 *struct { 1400 - Errors *struct { 1401 - Error *string `json:"error,omitempty"` 1402 - ErrorDescription *string `json:"error_description,omitempty"` 1403 - } `json:"errors,omitempty"` 1404 - } 1405 - JSON500 *struct { 1406 - Errors *struct { 1407 - Code *string `json:"code,omitempty"` 1408 - Reason *string `json:"reason,omitempty"` 1409 - Timestamp *float32 `json:"timestamp,omitempty"` 1410 - } `json:"errors,omitempty"` 1411 - } 1412 - } 1413 - 1414 - // Status returns HTTPResponse.Status 1415 - func (r LocationListResponse) Status() string { 1416 - if r.HTTPResponse != nil { 1417 - return r.HTTPResponse.Status 1418 - } 1419 - return http.StatusText(0) 1420 - } 1421 - 1422 - // StatusCode returns HTTPResponse.StatusCode 1423 - func (r LocationListResponse) StatusCode() int { 1424 - if r.HTTPResponse != nil { 1425 - return r.HTTPResponse.StatusCode 1426 - } 1427 - return 0 1428 - } 1429 - 1430 - type LocationDetailsResponse struct { 1431 - Body []byte 1432 - HTTPResponse *http.Response 1433 - JSON200 *struct { 1434 - Data *struct { 1435 - Address *struct { 1436 - AddressLine1 *string `json:"addressLine1,omitempty"` 1437 - AddressLine2 *string `json:"addressLine2,omitempty"` 1438 - City *string `json:"city,omitempty"` 1439 - County *string `json:"county,omitempty"` 1440 - State *string `json:"state,omitempty"` 1441 - ZipCode *string `json:"zipCode,omitempty"` 1442 - } `json:"address,omitempty"` 1443 - Chain *string `json:"chain,omitempty"` 1444 - Departments *[]struct { 1445 - DepartmentId *string `json:"departmentId,omitempty"` 1446 - Hours *struct { 1447 - Open24 *bool `json:"Open24,omitempty"` 1448 - Friday *struct { 1449 - Close *string `json:"close,omitempty"` 1450 - Open *string `json:"open,omitempty"` 1451 - Open24 *bool `json:"open24,omitempty"` 1452 - } `json:"friday,omitempty"` 1453 - Monday *struct { 1454 - Close *string `json:"close,omitempty"` 1455 - Open *string `json:"open,omitempty"` 1456 - Open24 *bool `json:"open24,omitempty"` 1457 - } `json:"monday,omitempty"` 1458 - Saturday *struct { 1459 - Close *string `json:"close,omitempty"` 1460 - Open *string `json:"open,omitempty"` 1461 - Open24 *bool `json:"open24,omitempty"` 1462 - } `json:"saturday,omitempty"` 1463 - Sunday *struct { 1464 - Close *string `json:"close,omitempty"` 1465 - Open *string `json:"open,omitempty"` 1466 - Open24 *bool `json:"open24,omitempty"` 1467 - } `json:"sunday,omitempty"` 1468 - Thursday *struct { 1469 - Close *string `json:"close,omitempty"` 1470 - Open *string `json:"open,omitempty"` 1471 - Open24 *bool `json:"open24,omitempty"` 1472 - } `json:"thursday,omitempty"` 1473 - Tuesday *struct { 1474 - Close *string `json:"close,omitempty"` 1475 - Open *string `json:"open,omitempty"` 1476 - Open24 *bool `json:"open24,omitempty"` 1477 - } `json:"tuesday,omitempty"` 1478 - Wednesday *struct { 1479 - Close *string `json:"close,omitempty"` 1480 - Open *string `json:"open,omitempty"` 1481 - Open24 *bool `json:"open24,omitempty"` 1482 - } `json:"wednesday,omitempty"` 1483 - } `json:"hours,omitempty"` 1484 - Name *string `json:"name,omitempty"` 1485 - Phone *string `json:"phone,omitempty"` 1486 - } `json:"departments,omitempty"` 1487 - DivisionNumber *string `json:"divisionNumber,omitempty"` 1488 - Geolocation *struct { 1489 - LatLng *string `json:"latLng,omitempty"` 1490 - Latitude *float32 `json:"latitude,omitempty"` 1491 - Longitude *float32 `json:"longitude,omitempty"` 1492 - } `json:"geolocation,omitempty"` 1493 - Hours *struct { 1494 - Open24 *bool `json:"Open24,omitempty"` 1495 - Friday *struct { 1496 - Close *string `json:"close,omitempty"` 1497 - Open *string `json:"open,omitempty"` 1498 - Open24 *bool `json:"open24,omitempty"` 1499 - } `json:"friday,omitempty"` 1500 - GmtOffset *string `json:"gmtOffset,omitempty"` 1501 - Monday *struct { 1502 - Close *string `json:"close,omitempty"` 1503 - Open *string `json:"open,omitempty"` 1504 - Open24 *bool `json:"open24,omitempty"` 1505 - } `json:"monday,omitempty"` 1506 - Saturday *struct { 1507 - Close *string `json:"close,omitempty"` 1508 - Open *string `json:"open,omitempty"` 1509 - Open24 *bool `json:"open24,omitempty"` 1510 - } `json:"saturday,omitempty"` 1511 - Sunday *struct { 1512 - Close *string `json:"close,omitempty"` 1513 - Open *string `json:"open,omitempty"` 1514 - Open24 *bool `json:"open24,omitempty"` 1515 - } `json:"sunday,omitempty"` 1516 - Thursday *struct { 1517 - Close *string `json:"close,omitempty"` 1518 - Open *string `json:"open,omitempty"` 1519 - Open24 *bool `json:"open24,omitempty"` 1520 - } `json:"thursday,omitempty"` 1521 - Timezone *string `json:"timezone,omitempty"` 1522 - Tuesday *struct { 1523 - Close *string `json:"close,omitempty"` 1524 - Open *string `json:"open,omitempty"` 1525 - Open24 *bool `json:"open24,omitempty"` 1526 - } `json:"tuesday,omitempty"` 1527 - Wednesday *struct { 1528 - Close *string `json:"close,omitempty"` 1529 - Open *string `json:"open,omitempty"` 1530 - Open24 *bool `json:"open24,omitempty"` 1531 - } `json:"wednesday,omitempty"` 1532 - } `json:"hours,omitempty"` 1533 - LocationId *string `json:"locationId,omitempty"` 1534 - Name *string `json:"name,omitempty"` 1535 - Phone *string `json:"phone,omitempty"` 1536 - StoreNumber *string `json:"storeNumber,omitempty"` 1537 - } `json:"data,omitempty"` 1538 - Meta *struct { 1539 - Pagination *struct { 1540 - Limit *float32 `json:"limit,omitempty"` 1541 - Start *float32 `json:"start,omitempty"` 1542 - Total *float32 `json:"total,omitempty"` 1543 - } `json:"pagination,omitempty"` 1544 - Warnings *[]string `json:"warnings,omitempty"` 1545 - } `json:"meta,omitempty"` 1546 - } 1547 - JSON400 *struct { 1548 - Errors *struct { 1549 - Code *string `json:"code,omitempty"` 1550 - Reason *string `json:"reason,omitempty"` 1551 - Timestamp *float32 `json:"timestamp,omitempty"` 1552 - } `json:"errors,omitempty"` 1553 - } 1554 - JSON401 *struct { 1555 - Errors *struct { 1556 - Error *string `json:"error,omitempty"` 1557 - ErrorDescription *string `json:"error_description,omitempty"` 1558 - } `json:"errors,omitempty"` 1559 - } 1560 - JSON500 *struct { 1561 - Errors *struct { 1562 - Code *string `json:"code,omitempty"` 1563 - Reason *string `json:"reason,omitempty"` 1564 - Timestamp *float32 `json:"timestamp,omitempty"` 1565 - } `json:"errors,omitempty"` 1566 - } 1567 - } 1568 - 1569 - // Status returns HTTPResponse.Status 1570 - func (r LocationDetailsResponse) Status() string { 1571 - if r.HTTPResponse != nil { 1572 - return r.HTTPResponse.Status 1573 - } 1574 - return http.StatusText(0) 1575 - } 1576 - 1577 - // StatusCode returns HTTPResponse.StatusCode 1578 - func (r LocationDetailsResponse) StatusCode() int { 1579 - if r.HTTPResponse != nil { 1580 - return r.HTTPResponse.StatusCode 1581 - } 1582 - return 0 1583 - } 1584 - 1585 - type ProductSearchResponse struct { 1586 - Body []byte 1587 - HTTPResponse *http.Response 1588 - JSON200 *struct { 1589 - Data *[]struct { 1590 - AisleLocations *[]struct { 1591 - BayNumber *string `json:"bayNumber,omitempty"` 1592 - Description *string `json:"description,omitempty"` 1593 - Number *string `json:"number,omitempty"` 1594 - NumberOfFacings *string `json:"numberOfFacings,omitempty"` 1595 - SequenceNumber *string `json:"sequenceNumber,omitempty"` 1596 - ShelfNumber *string `json:"shelfNumber,omitempty"` 1597 - ShelfPositionInBay *string `json:"shelfPositionInBay,omitempty"` 1598 - Side *string `json:"side,omitempty"` 1599 - } `json:"aisleLocations,omitempty"` 1600 - Brand *string `json:"brand,omitempty"` 1601 - Categories *[]string `json:"categories,omitempty"` 1602 - CountryOrigin *string `json:"countryOrigin,omitempty"` 1603 - Description *string `json:"description,omitempty"` 1604 - Images *[]struct { 1605 - Default *bool `json:"default,omitempty"` 1606 - Type *string `json:"type,omitempty"` 1607 - } `json:"images,omitempty"` 1608 - ItemInformation *struct { 1609 - Depth *string `json:"depth,omitempty"` 1610 - Height *string `json:"height,omitempty"` 1611 - Width *string `json:"width,omitempty"` 1612 - } `json:"itemInformation,omitempty"` 1613 - Items *[]struct { 1614 - Favorite *bool `json:"favorite,omitempty"` 1615 - Fulfillment *struct { 1616 - Curbside *bool `json:"curbside,omitempty"` 1617 - Delivery *bool `json:"delivery,omitempty"` 1618 - } `json:"fulfillment,omitempty"` 1619 - Inventory *struct { 1620 - StockLevel *string `json:"stockLevel,omitempty"` 1621 - } `json:"inventory,omitempty"` 1622 - ItemId *string `json:"itemId,omitempty"` 1623 - Price *struct { 1624 - Promo *float32 `json:"promo,omitempty"` 1625 - Regular *float32 `json:"regular,omitempty"` 1626 - } `json:"price,omitempty"` 1627 - Size *string `json:"size,omitempty"` 1628 - } `json:"items,omitempty"` 1629 - ProductId *string `json:"productId,omitempty"` 1630 - Temperature *struct { 1631 - HeatSensitive *bool `json:"heatSensitive,omitempty"` 1632 - Indicator *string `json:"indicator,omitempty"` 1633 - } `json:"temperature,omitempty"` 1634 - Upc *string `json:"upc,omitempty"` 1635 - } `json:"data,omitempty"` 1636 - Meta *struct { 1637 - Pagination *struct { 1638 - Limit *float32 `json:"limit,omitempty"` 1639 - Start *float32 `json:"start,omitempty"` 1640 - Total *float32 `json:"total,omitempty"` 1641 - } `json:"pagination,omitempty"` 1642 - Warnings *[]string `json:"warnings,omitempty"` 1643 - } `json:"meta,omitempty"` 1644 - } 1645 - JSON400 *struct { 1646 - Errors *struct { 1647 - Code *string `json:"code,omitempty"` 1648 - Reason *string `json:"reason,omitempty"` 1649 - Timestamp *float32 `json:"timestamp,omitempty"` 1650 - } `json:"errors,omitempty"` 1651 - } 1652 - JSON401 *struct { 1653 - Errors *struct { 1654 - Error *string `json:"error,omitempty"` 1655 - ErrorDescription *string `json:"error_description,omitempty"` 1656 - } `json:"errors,omitempty"` 1657 - } 1658 - JSON500 *struct { 1659 - Errors *struct { 1660 - Code *string `json:"code,omitempty"` 1661 - Reason *string `json:"reason,omitempty"` 1662 - Timestamp *float32 `json:"timestamp,omitempty"` 1663 - } `json:"errors,omitempty"` 1664 - } 1665 - } 1666 - 1667 - // Status returns HTTPResponse.Status 1668 - func (r ProductSearchResponse) Status() string { 1669 - if r.HTTPResponse != nil { 1670 - return r.HTTPResponse.Status 1671 - } 1672 - return http.StatusText(0) 1673 - } 1674 - 1675 - // StatusCode returns HTTPResponse.StatusCode 1676 - func (r ProductSearchResponse) StatusCode() int { 1677 - if r.HTTPResponse != nil { 1678 - return r.HTTPResponse.StatusCode 1679 - } 1680 - return 0 1681 - } 1682 - 1683 - type ProductDetailsResponse struct { 1684 - Body []byte 1685 - HTTPResponse *http.Response 1686 - JSON200 *struct { 1687 - Data *struct { 1688 - AisleLocations *[]struct { 1689 - BayNumber *string `json:"bayNumber,omitempty"` 1690 - Description *string `json:"description,omitempty"` 1691 - Number *string `json:"number,omitempty"` 1692 - NumberOfFacings *string `json:"numberOfFacings,omitempty"` 1693 - SequenceNumber *string `json:"sequenceNumber,omitempty"` 1694 - ShelfNumber *string `json:"shelfNumber,omitempty"` 1695 - ShelfPositionInBay *string `json:"shelfPositionInBay,omitempty"` 1696 - Side *string `json:"side,omitempty"` 1697 - } `json:"aisleLocations,omitempty"` 1698 - Brand *string `json:"brand,omitempty"` 1699 - Categories *[]string `json:"categories,omitempty"` 1700 - CountryOrigin *string `json:"countryOrigin,omitempty"` 1701 - Description *string `json:"description,omitempty"` 1702 - Images *[]struct { 1703 - Default *bool `json:"default,omitempty"` 1704 - Type *string `json:"type,omitempty"` 1705 - } `json:"images,omitempty"` 1706 - ItemInformation *struct { 1707 - Depth *string `json:"depth,omitempty"` 1708 - Height *string `json:"height,omitempty"` 1709 - Width *string `json:"width,omitempty"` 1710 - } `json:"itemInformation,omitempty"` 1711 - Items *[]struct { 1712 - Favorite *bool `json:"favorite,omitempty"` 1713 - Fulfillment *struct { 1714 - Curbside *bool `json:"curbside,omitempty"` 1715 - Delivery *bool `json:"delivery,omitempty"` 1716 - } `json:"fulfillment,omitempty"` 1717 - Inventory *struct { 1718 - StockLevel *string `json:"stockLevel,omitempty"` 1719 - } `json:"inventory,omitempty"` 1720 - ItemId *string `json:"itemId,omitempty"` 1721 - Price *struct { 1722 - Promo *float32 `json:"promo,omitempty"` 1723 - Regular *float32 `json:"regular,omitempty"` 1724 - } `json:"price,omitempty"` 1725 - Size *string `json:"size,omitempty"` 1726 - } `json:"items,omitempty"` 1727 - ProductId *string `json:"productId,omitempty"` 1728 - Temperature *struct { 1729 - HeatSensitive *bool `json:"heatSensitive,omitempty"` 1730 - Indicator *string `json:"indicator,omitempty"` 1731 - } `json:"temperature,omitempty"` 1732 - Upc *string `json:"upc,omitempty"` 1733 - } `json:"data,omitempty"` 1734 - Meta *struct { 1735 - Pagination *struct { 1736 - Limit *float32 `json:"limit,omitempty"` 1737 - Start *float32 `json:"start,omitempty"` 1738 - Total *float32 `json:"total,omitempty"` 1739 - } `json:"pagination,omitempty"` 1740 - Warnings *[]string `json:"warnings,omitempty"` 1741 - } `json:"meta,omitempty"` 1742 - } 1743 - JSON400 *struct { 1744 - Errors *struct { 1745 - Code *string `json:"code,omitempty"` 1746 - Reason *string `json:"reason,omitempty"` 1747 - Timestamp *float32 `json:"timestamp,omitempty"` 1748 - } `json:"errors,omitempty"` 1749 - } 1750 - JSON401 *struct { 1751 - Errors *struct { 1752 - Error *string `json:"error,omitempty"` 1753 - ErrorDescription *string `json:"error_description,omitempty"` 1754 - } `json:"errors,omitempty"` 1755 - } 1756 - JSON500 *struct { 1757 - Errors *struct { 1758 - Code *string `json:"code,omitempty"` 1759 - Reason *string `json:"reason,omitempty"` 1760 - Timestamp *float32 `json:"timestamp,omitempty"` 1761 - } `json:"errors,omitempty"` 1762 - } 1763 - } 1764 - 1765 - // Status returns HTTPResponse.Status 1766 - func (r ProductDetailsResponse) Status() string { 1767 - if r.HTTPResponse != nil { 1768 - return r.HTTPResponse.Status 1769 - } 1770 - return http.StatusText(0) 1771 - } 1772 - 1773 - // StatusCode returns HTTPResponse.StatusCode 1774 - func (r ProductDetailsResponse) StatusCode() int { 1775 - if r.HTTPResponse != nil { 1776 - return r.HTTPResponse.StatusCode 1777 - } 1778 - return 0 1779 - } 1780 - 1781 - // AddToCartWithBodyWithResponse request with arbitrary body returning *AddToCartResponse 1782 - func (c *ClientWithResponses) AddToCartWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AddToCartResponse, error) { 1783 - rsp, err := c.AddToCartWithBody(ctx, contentType, body, reqEditors...) 1784 - if err != nil { 1785 - return nil, err 1786 - } 1787 - return ParseAddToCartResponse(rsp) 1788 - } 1789 - 1790 - func (c *ClientWithResponses) AddToCartWithResponse(ctx context.Context, body AddToCartJSONRequestBody, reqEditors ...RequestEditorFn) (*AddToCartResponse, error) { 1791 - rsp, err := c.AddToCart(ctx, body, reqEditors...) 1792 - if err != nil { 1793 - return nil, err 1794 - } 1795 - return ParseAddToCartResponse(rsp) 1796 - } 1797 - 1798 - // ChainListWithResponse request returning *ChainListResponse 1799 - func (c *ClientWithResponses) ChainListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ChainListResponse, error) { 1800 - rsp, err := c.ChainList(ctx, reqEditors...) 1801 - if err != nil { 1802 - return nil, err 1803 - } 1804 - return ParseChainListResponse(rsp) 1805 - } 1806 - 1807 - // ChainDetailsWithResponse request returning *ChainDetailsResponse 1808 - func (c *ClientWithResponses) ChainDetailsWithResponse(ctx context.Context, name string, reqEditors ...RequestEditorFn) (*ChainDetailsResponse, error) { 1809 - rsp, err := c.ChainDetails(ctx, name, reqEditors...) 1810 - if err != nil { 1811 - return nil, err 1812 - } 1813 - return ParseChainDetailsResponse(rsp) 1814 - } 1815 - 1816 - // DepartmentListWithResponse request returning *DepartmentListResponse 1817 - func (c *ClientWithResponses) DepartmentListWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DepartmentListResponse, error) { 1818 - rsp, err := c.DepartmentList(ctx, reqEditors...) 1819 - if err != nil { 1820 - return nil, err 1821 - } 1822 - return ParseDepartmentListResponse(rsp) 1823 - } 1824 - 1825 - // DepartmentDetailsWithResponse request returning *DepartmentDetailsResponse 1826 - func (c *ClientWithResponses) DepartmentDetailsWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DepartmentDetailsResponse, error) { 1827 - rsp, err := c.DepartmentDetails(ctx, id, reqEditors...) 1828 - if err != nil { 1829 - return nil, err 1830 - } 1831 - return ParseDepartmentDetailsResponse(rsp) 1832 - } 1833 - 1834 - // UserProfileInformationWithResponse request returning *UserProfileInformationResponse 1835 - func (c *ClientWithResponses) UserProfileInformationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*UserProfileInformationResponse, error) { 1836 - rsp, err := c.UserProfileInformation(ctx, reqEditors...) 1837 - if err != nil { 1838 - return nil, err 1839 - } 1840 - return ParseUserProfileInformationResponse(rsp) 1841 - } 1842 - 1843 - // LocationListWithResponse request returning *LocationListResponse 1844 - func (c *ClientWithResponses) LocationListWithResponse(ctx context.Context, params *LocationListParams, reqEditors ...RequestEditorFn) (*LocationListResponse, error) { 1845 - rsp, err := c.LocationList(ctx, params, reqEditors...) 1846 - if err != nil { 1847 - return nil, err 1848 - } 1849 - return ParseLocationListResponse(rsp) 1850 - } 1851 - 1852 - // LocationDetailsWithResponse request returning *LocationDetailsResponse 1853 - func (c *ClientWithResponses) LocationDetailsWithResponse(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*LocationDetailsResponse, error) { 1854 - rsp, err := c.LocationDetails(ctx, locationId, reqEditors...) 1855 - if err != nil { 1856 - return nil, err 1857 - } 1858 - return ParseLocationDetailsResponse(rsp) 1859 - } 1860 - 1861 - // ProductSearchWithResponse request returning *ProductSearchResponse 1862 - func (c *ClientWithResponses) ProductSearchWithResponse(ctx context.Context, params *ProductSearchParams, reqEditors ...RequestEditorFn) (*ProductSearchResponse, error) { 1863 - rsp, err := c.ProductSearch(ctx, params, reqEditors...) 1864 - if err != nil { 1865 - return nil, err 1866 - } 1867 - return ParseProductSearchResponse(rsp) 1868 - } 1869 - 1870 - // ProductDetailsWithResponse request returning *ProductDetailsResponse 1871 - func (c *ClientWithResponses) ProductDetailsWithResponse(ctx context.Context, id string, params *ProductDetailsParams, reqEditors ...RequestEditorFn) (*ProductDetailsResponse, error) { 1872 - rsp, err := c.ProductDetails(ctx, id, params, reqEditors...) 1873 - if err != nil { 1874 - return nil, err 1875 - } 1876 - return ParseProductDetailsResponse(rsp) 1877 - } 1878 - 1879 - // ParseAddToCartResponse parses an HTTP response from a AddToCartWithResponse call 1880 - func ParseAddToCartResponse(rsp *http.Response) (*AddToCartResponse, error) { 1881 - bodyBytes, err := io.ReadAll(rsp.Body) 1882 - defer func() { _ = rsp.Body.Close() }() 1883 - if err != nil { 1884 - return nil, err 1885 - } 1886 - 1887 - response := &AddToCartResponse{ 1888 - Body: bodyBytes, 1889 - HTTPResponse: rsp, 1890 - } 1891 - 1892 - switch { 1893 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 1894 - var dest struct { 1895 - Errors *string `json:"errors,omitempty"` 1896 - } 1897 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1898 - return nil, err 1899 - } 1900 - response.JSON400 = &dest 1901 - 1902 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 1903 - var dest struct { 1904 - Errors *struct { 1905 - Error *string `json:"error,omitempty"` 1906 - ErrorDescription *string `json:"error_description,omitempty"` 1907 - } `json:"errors,omitempty"` 1908 - } 1909 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1910 - return nil, err 1911 - } 1912 - response.JSON401 = &dest 1913 - 1914 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 1915 - var dest struct { 1916 - Errors *struct { 1917 - Code *string `json:"code,omitempty"` 1918 - Reason *string `json:"reason,omitempty"` 1919 - Timestamp *float32 `json:"timestamp,omitempty"` 1920 - } `json:"errors,omitempty"` 1921 - } 1922 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1923 - return nil, err 1924 - } 1925 - response.JSON500 = &dest 1926 - 1927 - } 1928 - 1929 - return response, nil 1930 - } 1931 - 1932 - // ParseChainListResponse parses an HTTP response from a ChainListWithResponse call 1933 - func ParseChainListResponse(rsp *http.Response) (*ChainListResponse, error) { 1934 - bodyBytes, err := io.ReadAll(rsp.Body) 1935 - defer func() { _ = rsp.Body.Close() }() 1936 - if err != nil { 1937 - return nil, err 1938 - } 1939 - 1940 - response := &ChainListResponse{ 1941 - Body: bodyBytes, 1942 - HTTPResponse: rsp, 1943 - } 1944 - 1945 - switch { 1946 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 1947 - var dest struct { 1948 - Data *[]struct { 1949 - DivisionNumbers *[]string `json:"divisionNumbers,omitempty"` 1950 - Name *string `json:"name,omitempty"` 1951 - } `json:"data,omitempty"` 1952 - Meta *struct { 1953 - Pagination *struct { 1954 - Limit *float32 `json:"limit,omitempty"` 1955 - Start *float32 `json:"start,omitempty"` 1956 - Total *float32 `json:"total,omitempty"` 1957 - } `json:"pagination,omitempty"` 1958 - Warnings *[]string `json:"warnings,omitempty"` 1959 - } `json:"meta,omitempty"` 1960 - } 1961 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1962 - return nil, err 1963 - } 1964 - response.JSON200 = &dest 1965 - 1966 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 1967 - var dest struct { 1968 - Errors *struct { 1969 - Code *string `json:"code,omitempty"` 1970 - Reason *string `json:"reason,omitempty"` 1971 - Timestamp *float32 `json:"timestamp,omitempty"` 1972 - } `json:"errors,omitempty"` 1973 - } 1974 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1975 - return nil, err 1976 - } 1977 - response.JSON400 = &dest 1978 - 1979 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 1980 - var dest struct { 1981 - Errors *struct { 1982 - Error *string `json:"error,omitempty"` 1983 - ErrorDescription *string `json:"error_description,omitempty"` 1984 - } `json:"errors,omitempty"` 1985 - } 1986 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 1987 - return nil, err 1988 - } 1989 - response.JSON401 = &dest 1990 - 1991 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 1992 - var dest struct { 1993 - Errors *struct { 1994 - Code *string `json:"code,omitempty"` 1995 - Reason *string `json:"reason,omitempty"` 1996 - Timestamp *float32 `json:"timestamp,omitempty"` 1997 - } `json:"errors,omitempty"` 1998 - } 1999 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2000 - return nil, err 2001 - } 2002 - response.JSON500 = &dest 2003 - 2004 - } 2005 - 2006 - return response, nil 2007 - } 2008 - 2009 - // ParseChainDetailsResponse parses an HTTP response from a ChainDetailsWithResponse call 2010 - func ParseChainDetailsResponse(rsp *http.Response) (*ChainDetailsResponse, error) { 2011 - bodyBytes, err := io.ReadAll(rsp.Body) 2012 - defer func() { _ = rsp.Body.Close() }() 2013 - if err != nil { 2014 - return nil, err 2015 - } 2016 - 2017 - response := &ChainDetailsResponse{ 2018 - Body: bodyBytes, 2019 - HTTPResponse: rsp, 2020 - } 2021 - 2022 - switch { 2023 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2024 - var dest struct { 2025 - Data *struct { 2026 - DivisionNumbers *[]string `json:"divisionNumbers,omitempty"` 2027 - Name *string `json:"name,omitempty"` 2028 - } `json:"data,omitempty"` 2029 - Meta *map[string]interface{} `json:"meta,omitempty"` 2030 - } 2031 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2032 - return nil, err 2033 - } 2034 - response.JSON200 = &dest 2035 - 2036 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2037 - var dest struct { 2038 - Errors *struct { 2039 - Error *string `json:"error,omitempty"` 2040 - ErrorDescription *string `json:"error_description,omitempty"` 2041 - } `json:"errors,omitempty"` 2042 - } 2043 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2044 - return nil, err 2045 - } 2046 - response.JSON401 = &dest 2047 - 2048 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2049 - var dest struct { 2050 - Errors *struct { 2051 - Code *string `json:"code,omitempty"` 2052 - Reason *string `json:"reason,omitempty"` 2053 - Timestamp *float32 `json:"timestamp,omitempty"` 2054 - } `json:"errors,omitempty"` 2055 - } 2056 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2057 - return nil, err 2058 - } 2059 - response.JSON500 = &dest 2060 - 2061 - } 2062 - 2063 - return response, nil 2064 - } 2065 - 2066 - // ParseDepartmentListResponse parses an HTTP response from a DepartmentListWithResponse call 2067 - func ParseDepartmentListResponse(rsp *http.Response) (*DepartmentListResponse, error) { 2068 - bodyBytes, err := io.ReadAll(rsp.Body) 2069 - defer func() { _ = rsp.Body.Close() }() 2070 - if err != nil { 2071 - return nil, err 2072 - } 2073 - 2074 - response := &DepartmentListResponse{ 2075 - Body: bodyBytes, 2076 - HTTPResponse: rsp, 2077 - } 2078 - 2079 - switch { 2080 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2081 - var dest struct { 2082 - Data *[]struct { 2083 - DepartmentId *string `json:"departmentId,omitempty"` 2084 - Name *string `json:"name,omitempty"` 2085 - } `json:"data,omitempty"` 2086 - Meta *struct { 2087 - Pagination *struct { 2088 - Limit *float32 `json:"limit,omitempty"` 2089 - Start *float32 `json:"start,omitempty"` 2090 - Total *float32 `json:"total,omitempty"` 2091 - } `json:"pagination,omitempty"` 2092 - Warnings *[]string `json:"warnings,omitempty"` 2093 - } `json:"meta,omitempty"` 2094 - } 2095 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2096 - return nil, err 2097 - } 2098 - response.JSON200 = &dest 2099 - 2100 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2101 - var dest struct { 2102 - Errors *struct { 2103 - Error *string `json:"error,omitempty"` 2104 - ErrorDescription *string `json:"error_description,omitempty"` 2105 - } `json:"errors,omitempty"` 2106 - } 2107 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2108 - return nil, err 2109 - } 2110 - response.JSON401 = &dest 2111 - 2112 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2113 - var dest struct { 2114 - Errors *struct { 2115 - Code *string `json:"code,omitempty"` 2116 - Reason *string `json:"reason,omitempty"` 2117 - Timestamp *float32 `json:"timestamp,omitempty"` 2118 - } `json:"errors,omitempty"` 2119 - } 2120 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2121 - return nil, err 2122 - } 2123 - response.JSON500 = &dest 2124 - 2125 - } 2126 - 2127 - return response, nil 2128 - } 2129 - 2130 - // ParseDepartmentDetailsResponse parses an HTTP response from a DepartmentDetailsWithResponse call 2131 - func ParseDepartmentDetailsResponse(rsp *http.Response) (*DepartmentDetailsResponse, error) { 2132 - bodyBytes, err := io.ReadAll(rsp.Body) 2133 - defer func() { _ = rsp.Body.Close() }() 2134 - if err != nil { 2135 - return nil, err 2136 - } 2137 - 2138 - response := &DepartmentDetailsResponse{ 2139 - Body: bodyBytes, 2140 - HTTPResponse: rsp, 2141 - } 2142 - 2143 - switch { 2144 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2145 - var dest struct { 2146 - Data *struct { 2147 - DepartmentId *string `json:"departmentId,omitempty"` 2148 - Name *string `json:"name,omitempty"` 2149 - } `json:"data,omitempty"` 2150 - Meta *map[string]interface{} `json:"meta,omitempty"` 2151 - } 2152 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2153 - return nil, err 2154 - } 2155 - response.JSON200 = &dest 2156 - 2157 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 2158 - var dest struct { 2159 - Errors *struct { 2160 - Code *string `json:"code,omitempty"` 2161 - Reason *string `json:"reason,omitempty"` 2162 - Timestamp *float32 `json:"timestamp,omitempty"` 2163 - } `json:"errors,omitempty"` 2164 - } 2165 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2166 - return nil, err 2167 - } 2168 - response.JSON400 = &dest 2169 - 2170 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2171 - var dest struct { 2172 - Errors *struct { 2173 - Error *string `json:"error,omitempty"` 2174 - ErrorDescription *string `json:"error_description,omitempty"` 2175 - } `json:"errors,omitempty"` 2176 - } 2177 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2178 - return nil, err 2179 - } 2180 - response.JSON401 = &dest 2181 - 2182 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2183 - var dest struct { 2184 - Errors *struct { 2185 - Code *string `json:"code,omitempty"` 2186 - Reason *string `json:"reason,omitempty"` 2187 - Timestamp *float32 `json:"timestamp,omitempty"` 2188 - } `json:"errors,omitempty"` 2189 - } 2190 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2191 - return nil, err 2192 - } 2193 - response.JSON500 = &dest 2194 - 2195 - } 2196 - 2197 - return response, nil 2198 - } 2199 - 2200 - // ParseUserProfileInformationResponse parses an HTTP response from a UserProfileInformationWithResponse call 2201 - func ParseUserProfileInformationResponse(rsp *http.Response) (*UserProfileInformationResponse, error) { 2202 - bodyBytes, err := io.ReadAll(rsp.Body) 2203 - defer func() { _ = rsp.Body.Close() }() 2204 - if err != nil { 2205 - return nil, err 2206 - } 2207 - 2208 - response := &UserProfileInformationResponse{ 2209 - Body: bodyBytes, 2210 - HTTPResponse: rsp, 2211 - } 2212 - 2213 - switch { 2214 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2215 - var dest struct { 2216 - Data *struct { 2217 - Id *map[string]interface{} `json:"id,omitempty"` 2218 - } `json:"data,omitempty"` 2219 - Meta *struct { 2220 - Pagination *struct { 2221 - Limit *float32 `json:"limit,omitempty"` 2222 - Start *float32 `json:"start,omitempty"` 2223 - Total *float32 `json:"total,omitempty"` 2224 - } `json:"pagination,omitempty"` 2225 - Warnings *[]string `json:"warnings,omitempty"` 2226 - } `json:"meta,omitempty"` 2227 - } 2228 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2229 - return nil, err 2230 - } 2231 - response.JSON200 = &dest 2232 - 2233 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2234 - var dest struct { 2235 - Errors *struct { 2236 - Error *string `json:"error,omitempty"` 2237 - ErrorDescription *string `json:"error_description,omitempty"` 2238 - } `json:"errors,omitempty"` 2239 - } 2240 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2241 - return nil, err 2242 - } 2243 - response.JSON401 = &dest 2244 - 2245 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: 2246 - var dest struct { 2247 - Errors *struct { 2248 - Code *string `json:"code,omitempty"` 2249 - Reason *string `json:"reason,omitempty"` 2250 - Timestamp *float32 `json:"timestamp,omitempty"` 2251 - } `json:"errors,omitempty"` 2252 - } 2253 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2254 - return nil, err 2255 - } 2256 - response.JSON403 = &dest 2257 - 2258 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2259 - var dest struct { 2260 - Errors *struct { 2261 - Code *string `json:"code,omitempty"` 2262 - Reason *string `json:"reason,omitempty"` 2263 - Timestamp *float32 `json:"timestamp,omitempty"` 2264 - } `json:"errors,omitempty"` 2265 - } 2266 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2267 - return nil, err 2268 - } 2269 - response.JSON500 = &dest 2270 - 2271 - } 2272 - 2273 - return response, nil 2274 - } 2275 - 2276 - // ParseLocationListResponse parses an HTTP response from a LocationListWithResponse call 2277 - func ParseLocationListResponse(rsp *http.Response) (*LocationListResponse, error) { 2278 - bodyBytes, err := io.ReadAll(rsp.Body) 2279 - defer func() { _ = rsp.Body.Close() }() 2280 - if err != nil { 2281 - return nil, err 2282 - } 2283 - 2284 - response := &LocationListResponse{ 2285 - Body: bodyBytes, 2286 - HTTPResponse: rsp, 2287 - } 2288 - 2289 - switch { 2290 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2291 - var dest struct { 2292 - Data *[]struct { 2293 - Address *struct { 2294 - AddressLine1 *string `json:"addressLine1,omitempty"` 2295 - AddressLine2 *string `json:"addressLine2,omitempty"` 2296 - City *string `json:"city,omitempty"` 2297 - County *string `json:"county,omitempty"` 2298 - State *string `json:"state,omitempty"` 2299 - ZipCode *string `json:"zipCode,omitempty"` 2300 - } `json:"address,omitempty"` 2301 - Chain *string `json:"chain,omitempty"` 2302 - Departments *[]struct { 2303 - DepartmentId *string `json:"departmentId,omitempty"` 2304 - Hours *struct { 2305 - Open24 *bool `json:"Open24,omitempty"` 2306 - Friday *struct { 2307 - Close *string `json:"close,omitempty"` 2308 - Open *string `json:"open,omitempty"` 2309 - Open24 *bool `json:"open24,omitempty"` 2310 - } `json:"friday,omitempty"` 2311 - Monday *struct { 2312 - Close *string `json:"close,omitempty"` 2313 - Open *string `json:"open,omitempty"` 2314 - Open24 *bool `json:"open24,omitempty"` 2315 - } `json:"monday,omitempty"` 2316 - Saturday *struct { 2317 - Close *string `json:"close,omitempty"` 2318 - Open *string `json:"open,omitempty"` 2319 - Open24 *bool `json:"open24,omitempty"` 2320 - } `json:"saturday,omitempty"` 2321 - Sunday *struct { 2322 - Close *string `json:"close,omitempty"` 2323 - Open *string `json:"open,omitempty"` 2324 - Open24 *bool `json:"open24,omitempty"` 2325 - } `json:"sunday,omitempty"` 2326 - Thursday *struct { 2327 - Close *string `json:"close,omitempty"` 2328 - Open *string `json:"open,omitempty"` 2329 - Open24 *bool `json:"open24,omitempty"` 2330 - } `json:"thursday,omitempty"` 2331 - Tuesday *struct { 2332 - Close *string `json:"close,omitempty"` 2333 - Open *string `json:"open,omitempty"` 2334 - Open24 *bool `json:"open24,omitempty"` 2335 - } `json:"tuesday,omitempty"` 2336 - Wednesday *struct { 2337 - Close *string `json:"close,omitempty"` 2338 - Open *string `json:"open,omitempty"` 2339 - Open24 *bool `json:"open24,omitempty"` 2340 - } `json:"wednesday,omitempty"` 2341 - } `json:"hours,omitempty"` 2342 - Name *string `json:"name,omitempty"` 2343 - Phone *string `json:"phone,omitempty"` 2344 - } `json:"departments,omitempty"` 2345 - DivisionNumber *string `json:"divisionNumber,omitempty"` 2346 - Geolocation *struct { 2347 - LatLng *string `json:"latLng,omitempty"` 2348 - Latitude *float32 `json:"latitude,omitempty"` 2349 - Longitude *float32 `json:"longitude,omitempty"` 2350 - } `json:"geolocation,omitempty"` 2351 - Hours *struct { 2352 - Open24 *bool `json:"Open24,omitempty"` 2353 - Friday *struct { 2354 - Close *string `json:"close,omitempty"` 2355 - Open *string `json:"open,omitempty"` 2356 - Open24 *bool `json:"open24,omitempty"` 2357 - } `json:"friday,omitempty"` 2358 - GmtOffset *string `json:"gmtOffset,omitempty"` 2359 - Monday *struct { 2360 - Close *string `json:"close,omitempty"` 2361 - Open *string `json:"open,omitempty"` 2362 - Open24 *bool `json:"open24,omitempty"` 2363 - } `json:"monday,omitempty"` 2364 - Saturday *struct { 2365 - Close *string `json:"close,omitempty"` 2366 - Open *string `json:"open,omitempty"` 2367 - Open24 *bool `json:"open24,omitempty"` 2368 - } `json:"saturday,omitempty"` 2369 - Sunday *struct { 2370 - Close *string `json:"close,omitempty"` 2371 - Open *string `json:"open,omitempty"` 2372 - Open24 *bool `json:"open24,omitempty"` 2373 - } `json:"sunday,omitempty"` 2374 - Thursday *struct { 2375 - Close *string `json:"close,omitempty"` 2376 - Open *string `json:"open,omitempty"` 2377 - Open24 *bool `json:"open24,omitempty"` 2378 - } `json:"thursday,omitempty"` 2379 - Timezone *string `json:"timezone,omitempty"` 2380 - Tuesday *struct { 2381 - Close *string `json:"close,omitempty"` 2382 - Open *string `json:"open,omitempty"` 2383 - Open24 *bool `json:"open24,omitempty"` 2384 - } `json:"tuesday,omitempty"` 2385 - Wednesday *struct { 2386 - Close *string `json:"close,omitempty"` 2387 - Open *string `json:"open,omitempty"` 2388 - Open24 *bool `json:"open24,omitempty"` 2389 - } `json:"wednesday,omitempty"` 2390 - } `json:"hours,omitempty"` 2391 - LocationId *string `json:"locationId,omitempty"` 2392 - Name *string `json:"name,omitempty"` 2393 - Phone *string `json:"phone,omitempty"` 2394 - StoreNumber *string `json:"storeNumber,omitempty"` 2395 - } `json:"data,omitempty"` 2396 - Meta *struct { 2397 - Pagination *struct { 2398 - Limit *float32 `json:"limit,omitempty"` 2399 - Start *float32 `json:"start,omitempty"` 2400 - Total *float32 `json:"total,omitempty"` 2401 - } `json:"pagination,omitempty"` 2402 - Warnings *[]string `json:"warnings,omitempty"` 2403 - } `json:"meta,omitempty"` 2404 - } 2405 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2406 - return nil, err 2407 - } 2408 - response.JSON200 = &dest 2409 - 2410 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 2411 - var dest struct { 2412 - Errors *struct { 2413 - Code *string `json:"code,omitempty"` 2414 - Reason *string `json:"reason,omitempty"` 2415 - Timestamp *float32 `json:"timestamp,omitempty"` 2416 - } `json:"errors,omitempty"` 2417 - } 2418 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2419 - return nil, err 2420 - } 2421 - response.JSON400 = &dest 2422 - 2423 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2424 - var dest struct { 2425 - Errors *struct { 2426 - Error *string `json:"error,omitempty"` 2427 - ErrorDescription *string `json:"error_description,omitempty"` 2428 - } `json:"errors,omitempty"` 2429 - } 2430 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2431 - return nil, err 2432 - } 2433 - response.JSON401 = &dest 2434 - 2435 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2436 - var dest struct { 2437 - Errors *struct { 2438 - Code *string `json:"code,omitempty"` 2439 - Reason *string `json:"reason,omitempty"` 2440 - Timestamp *float32 `json:"timestamp,omitempty"` 2441 - } `json:"errors,omitempty"` 2442 - } 2443 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2444 - return nil, err 2445 - } 2446 - response.JSON500 = &dest 2447 - 2448 - } 2449 - 2450 - return response, nil 2451 - } 2452 - 2453 - // ParseLocationDetailsResponse parses an HTTP response from a LocationDetailsWithResponse call 2454 - func ParseLocationDetailsResponse(rsp *http.Response) (*LocationDetailsResponse, error) { 2455 - bodyBytes, err := io.ReadAll(rsp.Body) 2456 - defer func() { _ = rsp.Body.Close() }() 2457 - if err != nil { 2458 - return nil, err 2459 - } 2460 - 2461 - response := &LocationDetailsResponse{ 2462 - Body: bodyBytes, 2463 - HTTPResponse: rsp, 2464 - } 2465 - 2466 - switch { 2467 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2468 - var dest struct { 2469 - Data *struct { 2470 - Address *struct { 2471 - AddressLine1 *string `json:"addressLine1,omitempty"` 2472 - AddressLine2 *string `json:"addressLine2,omitempty"` 2473 - City *string `json:"city,omitempty"` 2474 - County *string `json:"county,omitempty"` 2475 - State *string `json:"state,omitempty"` 2476 - ZipCode *string `json:"zipCode,omitempty"` 2477 - } `json:"address,omitempty"` 2478 - Chain *string `json:"chain,omitempty"` 2479 - Departments *[]struct { 2480 - DepartmentId *string `json:"departmentId,omitempty"` 2481 - Hours *struct { 2482 - Open24 *bool `json:"Open24,omitempty"` 2483 - Friday *struct { 2484 - Close *string `json:"close,omitempty"` 2485 - Open *string `json:"open,omitempty"` 2486 - Open24 *bool `json:"open24,omitempty"` 2487 - } `json:"friday,omitempty"` 2488 - Monday *struct { 2489 - Close *string `json:"close,omitempty"` 2490 - Open *string `json:"open,omitempty"` 2491 - Open24 *bool `json:"open24,omitempty"` 2492 - } `json:"monday,omitempty"` 2493 - Saturday *struct { 2494 - Close *string `json:"close,omitempty"` 2495 - Open *string `json:"open,omitempty"` 2496 - Open24 *bool `json:"open24,omitempty"` 2497 - } `json:"saturday,omitempty"` 2498 - Sunday *struct { 2499 - Close *string `json:"close,omitempty"` 2500 - Open *string `json:"open,omitempty"` 2501 - Open24 *bool `json:"open24,omitempty"` 2502 - } `json:"sunday,omitempty"` 2503 - Thursday *struct { 2504 - Close *string `json:"close,omitempty"` 2505 - Open *string `json:"open,omitempty"` 2506 - Open24 *bool `json:"open24,omitempty"` 2507 - } `json:"thursday,omitempty"` 2508 - Tuesday *struct { 2509 - Close *string `json:"close,omitempty"` 2510 - Open *string `json:"open,omitempty"` 2511 - Open24 *bool `json:"open24,omitempty"` 2512 - } `json:"tuesday,omitempty"` 2513 - Wednesday *struct { 2514 - Close *string `json:"close,omitempty"` 2515 - Open *string `json:"open,omitempty"` 2516 - Open24 *bool `json:"open24,omitempty"` 2517 - } `json:"wednesday,omitempty"` 2518 - } `json:"hours,omitempty"` 2519 - Name *string `json:"name,omitempty"` 2520 - Phone *string `json:"phone,omitempty"` 2521 - } `json:"departments,omitempty"` 2522 - DivisionNumber *string `json:"divisionNumber,omitempty"` 2523 - Geolocation *struct { 2524 - LatLng *string `json:"latLng,omitempty"` 2525 - Latitude *float32 `json:"latitude,omitempty"` 2526 - Longitude *float32 `json:"longitude,omitempty"` 2527 - } `json:"geolocation,omitempty"` 2528 - Hours *struct { 2529 - Open24 *bool `json:"Open24,omitempty"` 2530 - Friday *struct { 2531 - Close *string `json:"close,omitempty"` 2532 - Open *string `json:"open,omitempty"` 2533 - Open24 *bool `json:"open24,omitempty"` 2534 - } `json:"friday,omitempty"` 2535 - GmtOffset *string `json:"gmtOffset,omitempty"` 2536 - Monday *struct { 2537 - Close *string `json:"close,omitempty"` 2538 - Open *string `json:"open,omitempty"` 2539 - Open24 *bool `json:"open24,omitempty"` 2540 - } `json:"monday,omitempty"` 2541 - Saturday *struct { 2542 - Close *string `json:"close,omitempty"` 2543 - Open *string `json:"open,omitempty"` 2544 - Open24 *bool `json:"open24,omitempty"` 2545 - } `json:"saturday,omitempty"` 2546 - Sunday *struct { 2547 - Close *string `json:"close,omitempty"` 2548 - Open *string `json:"open,omitempty"` 2549 - Open24 *bool `json:"open24,omitempty"` 2550 - } `json:"sunday,omitempty"` 2551 - Thursday *struct { 2552 - Close *string `json:"close,omitempty"` 2553 - Open *string `json:"open,omitempty"` 2554 - Open24 *bool `json:"open24,omitempty"` 2555 - } `json:"thursday,omitempty"` 2556 - Timezone *string `json:"timezone,omitempty"` 2557 - Tuesday *struct { 2558 - Close *string `json:"close,omitempty"` 2559 - Open *string `json:"open,omitempty"` 2560 - Open24 *bool `json:"open24,omitempty"` 2561 - } `json:"tuesday,omitempty"` 2562 - Wednesday *struct { 2563 - Close *string `json:"close,omitempty"` 2564 - Open *string `json:"open,omitempty"` 2565 - Open24 *bool `json:"open24,omitempty"` 2566 - } `json:"wednesday,omitempty"` 2567 - } `json:"hours,omitempty"` 2568 - LocationId *string `json:"locationId,omitempty"` 2569 - Name *string `json:"name,omitempty"` 2570 - Phone *string `json:"phone,omitempty"` 2571 - StoreNumber *string `json:"storeNumber,omitempty"` 2572 - } `json:"data,omitempty"` 2573 - Meta *struct { 2574 - Pagination *struct { 2575 - Limit *float32 `json:"limit,omitempty"` 2576 - Start *float32 `json:"start,omitempty"` 2577 - Total *float32 `json:"total,omitempty"` 2578 - } `json:"pagination,omitempty"` 2579 - Warnings *[]string `json:"warnings,omitempty"` 2580 - } `json:"meta,omitempty"` 2581 - } 2582 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2583 - return nil, err 2584 - } 2585 - response.JSON200 = &dest 2586 - 2587 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 2588 - var dest struct { 2589 - Errors *struct { 2590 - Code *string `json:"code,omitempty"` 2591 - Reason *string `json:"reason,omitempty"` 2592 - Timestamp *float32 `json:"timestamp,omitempty"` 2593 - } `json:"errors,omitempty"` 2594 - } 2595 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2596 - return nil, err 2597 - } 2598 - response.JSON400 = &dest 2599 - 2600 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2601 - var dest struct { 2602 - Errors *struct { 2603 - Error *string `json:"error,omitempty"` 2604 - ErrorDescription *string `json:"error_description,omitempty"` 2605 - } `json:"errors,omitempty"` 2606 - } 2607 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2608 - return nil, err 2609 - } 2610 - response.JSON401 = &dest 2611 - 2612 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2613 - var dest struct { 2614 - Errors *struct { 2615 - Code *string `json:"code,omitempty"` 2616 - Reason *string `json:"reason,omitempty"` 2617 - Timestamp *float32 `json:"timestamp,omitempty"` 2618 - } `json:"errors,omitempty"` 2619 - } 2620 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2621 - return nil, err 2622 - } 2623 - response.JSON500 = &dest 2624 - 2625 - } 2626 - 2627 - return response, nil 2628 - } 2629 - 2630 - // ParseProductSearchResponse parses an HTTP response from a ProductSearchWithResponse call 2631 - func ParseProductSearchResponse(rsp *http.Response) (*ProductSearchResponse, error) { 2632 - bodyBytes, err := io.ReadAll(rsp.Body) 2633 - defer func() { _ = rsp.Body.Close() }() 2634 - if err != nil { 2635 - return nil, err 2636 - } 2637 - 2638 - response := &ProductSearchResponse{ 2639 - Body: bodyBytes, 2640 - HTTPResponse: rsp, 2641 - } 2642 - 2643 - switch { 2644 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2645 - var dest struct { 2646 - Data *[]struct { 2647 - AisleLocations *[]struct { 2648 - BayNumber *string `json:"bayNumber,omitempty"` 2649 - Description *string `json:"description,omitempty"` 2650 - Number *string `json:"number,omitempty"` 2651 - NumberOfFacings *string `json:"numberOfFacings,omitempty"` 2652 - SequenceNumber *string `json:"sequenceNumber,omitempty"` 2653 - ShelfNumber *string `json:"shelfNumber,omitempty"` 2654 - ShelfPositionInBay *string `json:"shelfPositionInBay,omitempty"` 2655 - Side *string `json:"side,omitempty"` 2656 - } `json:"aisleLocations,omitempty"` 2657 - Brand *string `json:"brand,omitempty"` 2658 - Categories *[]string `json:"categories,omitempty"` 2659 - CountryOrigin *string `json:"countryOrigin,omitempty"` 2660 - Description *string `json:"description,omitempty"` 2661 - Images *[]struct { 2662 - Default *bool `json:"default,omitempty"` 2663 - Type *string `json:"type,omitempty"` 2664 - } `json:"images,omitempty"` 2665 - ItemInformation *struct { 2666 - Depth *string `json:"depth,omitempty"` 2667 - Height *string `json:"height,omitempty"` 2668 - Width *string `json:"width,omitempty"` 2669 - } `json:"itemInformation,omitempty"` 2670 - Items *[]struct { 2671 - Favorite *bool `json:"favorite,omitempty"` 2672 - Fulfillment *struct { 2673 - Curbside *bool `json:"curbside,omitempty"` 2674 - Delivery *bool `json:"delivery,omitempty"` 2675 - } `json:"fulfillment,omitempty"` 2676 - Inventory *struct { 2677 - StockLevel *string `json:"stockLevel,omitempty"` 2678 - } `json:"inventory,omitempty"` 2679 - ItemId *string `json:"itemId,omitempty"` 2680 - Price *struct { 2681 - Promo *float32 `json:"promo,omitempty"` 2682 - Regular *float32 `json:"regular,omitempty"` 2683 - } `json:"price,omitempty"` 2684 - Size *string `json:"size,omitempty"` 2685 - } `json:"items,omitempty"` 2686 - ProductId *string `json:"productId,omitempty"` 2687 - Temperature *struct { 2688 - HeatSensitive *bool `json:"heatSensitive,omitempty"` 2689 - Indicator *string `json:"indicator,omitempty"` 2690 - } `json:"temperature,omitempty"` 2691 - Upc *string `json:"upc,omitempty"` 2692 - } `json:"data,omitempty"` 2693 - Meta *struct { 2694 - Pagination *struct { 2695 - Limit *float32 `json:"limit,omitempty"` 2696 - Start *float32 `json:"start,omitempty"` 2697 - Total *float32 `json:"total,omitempty"` 2698 - } `json:"pagination,omitempty"` 2699 - Warnings *[]string `json:"warnings,omitempty"` 2700 - } `json:"meta,omitempty"` 2701 - } 2702 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2703 - return nil, err 2704 - } 2705 - response.JSON200 = &dest 2706 - 2707 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 2708 - var dest struct { 2709 - Errors *struct { 2710 - Code *string `json:"code,omitempty"` 2711 - Reason *string `json:"reason,omitempty"` 2712 - Timestamp *float32 `json:"timestamp,omitempty"` 2713 - } `json:"errors,omitempty"` 2714 - } 2715 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2716 - return nil, err 2717 - } 2718 - response.JSON400 = &dest 2719 - 2720 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2721 - var dest struct { 2722 - Errors *struct { 2723 - Error *string `json:"error,omitempty"` 2724 - ErrorDescription *string `json:"error_description,omitempty"` 2725 - } `json:"errors,omitempty"` 2726 - } 2727 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2728 - fmt.Printf("failing at 401: %s\n", bodyBytes) 2729 - return nil, err 2730 - } 2731 - response.JSON401 = &dest 2732 - 2733 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2734 - var dest struct { 2735 - Errors *struct { 2736 - Code *string `json:"code,omitempty"` 2737 - Reason *string `json:"reason,omitempty"` 2738 - Timestamp *float32 `json:"timestamp,omitempty"` 2739 - } `json:"errors,omitempty"` 2740 - } 2741 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2742 - return nil, err 2743 - } 2744 - response.JSON500 = &dest 2745 - 2746 - } 2747 - 2748 - return response, nil 2749 - } 2750 - 2751 - // ParseProductDetailsResponse parses an HTTP response from a ProductDetailsWithResponse call 2752 - func ParseProductDetailsResponse(rsp *http.Response) (*ProductDetailsResponse, error) { 2753 - bodyBytes, err := io.ReadAll(rsp.Body) 2754 - defer func() { _ = rsp.Body.Close() }() 2755 - if err != nil { 2756 - return nil, err 2757 - } 2758 - 2759 - response := &ProductDetailsResponse{ 2760 - Body: bodyBytes, 2761 - HTTPResponse: rsp, 2762 - } 2763 - 2764 - switch { 2765 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 2766 - var dest struct { 2767 - Data *struct { 2768 - AisleLocations *[]struct { 2769 - BayNumber *string `json:"bayNumber,omitempty"` 2770 - Description *string `json:"description,omitempty"` 2771 - Number *string `json:"number,omitempty"` 2772 - NumberOfFacings *string `json:"numberOfFacings,omitempty"` 2773 - SequenceNumber *string `json:"sequenceNumber,omitempty"` 2774 - ShelfNumber *string `json:"shelfNumber,omitempty"` 2775 - ShelfPositionInBay *string `json:"shelfPositionInBay,omitempty"` 2776 - Side *string `json:"side,omitempty"` 2777 - } `json:"aisleLocations,omitempty"` 2778 - Brand *string `json:"brand,omitempty"` 2779 - Categories *[]string `json:"categories,omitempty"` 2780 - CountryOrigin *string `json:"countryOrigin,omitempty"` 2781 - Description *string `json:"description,omitempty"` 2782 - Images *[]struct { 2783 - Default *bool `json:"default,omitempty"` 2784 - Type *string `json:"type,omitempty"` 2785 - } `json:"images,omitempty"` 2786 - ItemInformation *struct { 2787 - Depth *string `json:"depth,omitempty"` 2788 - Height *string `json:"height,omitempty"` 2789 - Width *string `json:"width,omitempty"` 2790 - } `json:"itemInformation,omitempty"` 2791 - Items *[]struct { 2792 - Favorite *bool `json:"favorite,omitempty"` 2793 - Fulfillment *struct { 2794 - Curbside *bool `json:"curbside,omitempty"` 2795 - Delivery *bool `json:"delivery,omitempty"` 2796 - } `json:"fulfillment,omitempty"` 2797 - Inventory *struct { 2798 - StockLevel *string `json:"stockLevel,omitempty"` 2799 - } `json:"inventory,omitempty"` 2800 - ItemId *string `json:"itemId,omitempty"` 2801 - Price *struct { 2802 - Promo *float32 `json:"promo,omitempty"` 2803 - Regular *float32 `json:"regular,omitempty"` 2804 - } `json:"price,omitempty"` 2805 - Size *string `json:"size,omitempty"` 2806 - } `json:"items,omitempty"` 2807 - ProductId *string `json:"productId,omitempty"` 2808 - Temperature *struct { 2809 - HeatSensitive *bool `json:"heatSensitive,omitempty"` 2810 - Indicator *string `json:"indicator,omitempty"` 2811 - } `json:"temperature,omitempty"` 2812 - Upc *string `json:"upc,omitempty"` 2813 - } `json:"data,omitempty"` 2814 - Meta *struct { 2815 - Pagination *struct { 2816 - Limit *float32 `json:"limit,omitempty"` 2817 - Start *float32 `json:"start,omitempty"` 2818 - Total *float32 `json:"total,omitempty"` 2819 - } `json:"pagination,omitempty"` 2820 - Warnings *[]string `json:"warnings,omitempty"` 2821 - } `json:"meta,omitempty"` 2822 - } 2823 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2824 - return nil, err 2825 - } 2826 - response.JSON200 = &dest 2827 - 2828 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 2829 - var dest struct { 2830 - Errors *struct { 2831 - Code *string `json:"code,omitempty"` 2832 - Reason *string `json:"reason,omitempty"` 2833 - Timestamp *float32 `json:"timestamp,omitempty"` 2834 - } `json:"errors,omitempty"` 2835 - } 2836 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2837 - return nil, err 2838 - } 2839 - response.JSON400 = &dest 2840 - 2841 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 2842 - var dest struct { 2843 - Errors *struct { 2844 - Error *string `json:"error,omitempty"` 2845 - ErrorDescription *string `json:"error_description,omitempty"` 2846 - } `json:"errors,omitempty"` 2847 - } 2848 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2849 - return nil, err 2850 - } 2851 - response.JSON401 = &dest 2852 - 2853 - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 2854 - var dest struct { 2855 - Errors *struct { 2856 - Code *string `json:"code,omitempty"` 2857 - Reason *string `json:"reason,omitempty"` 2858 - Timestamp *float32 `json:"timestamp,omitempty"` 2859 - } `json:"errors,omitempty"` 2860 - } 2861 - if err := json.Unmarshal(bodyBytes, &dest); err != nil { 2862 - return nil, err 2863 - } 2864 - response.JSON500 = &dest 2865 - 2866 - } 2867 - 2868 - return response, nil 2869 - }
+28 -35
internal/kroger/client.go
··· 12 12 "time" 13 13 14 14 "careme/internal/config" 15 + "careme/internal/kroger/products" 15 16 ) 16 17 17 - //go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml swagger.yaml 18 + //go:generate go generate ./products ./locations 18 19 19 20 // this wasn't in the swagger? try the jsons added next 20 - // OAuth2TokenResponse represents the response from Kroger OAuth2 token endpoint 21 - // LoggingDoer wraps an HttpRequestDoer and logs requests and responses 22 - type LoggingDoer struct { 23 - Wrapped HttpRequestDoer 24 - } 25 - 26 - func (l *LoggingDoer) Do(req *http.Request) (*http.Response, error) { 27 - fmt.Printf("Kroger Request: %s %s\nHeaders: %v\n", req.Method, req.URL.String(), req.Header) 28 - resp, err := l.Wrapped.Do(req) 29 - if err != nil { 30 - fmt.Printf("Kroger Response Error: %v\n", err) 31 - return resp, err 32 - } 33 - fmt.Printf("Kroger Response: %d %s\n", resp.StatusCode, resp.Status) 34 - return resp, err 35 - } 36 - 37 - type OAuth2TokenResponse struct { 21 + // oAuth2TokenResponse represents the response from Kroger OAuth2 token endpoint 22 + type oAuth2TokenResponse struct { 38 23 AccessToken string `json:"access_token"` 39 24 TokenType string `json:"token_type"` 40 25 ExpiresIn int `json:"expires_in"` ··· 47 32 expiresAt time.Time 48 33 clientID string 49 34 clientSecret string 35 + httpClient *http.Client 50 36 mu sync.Mutex 51 37 } 52 38 53 - func NewKrogerTokenManager(clientID, clientSecret string) *KrogerTokenManager { 39 + func NewKrogerTokenManager(clientID, clientSecret string, httpClient *http.Client) *KrogerTokenManager { 40 + if httpClient == nil { 41 + httpClient = http.DefaultClient 42 + } 54 43 return &KrogerTokenManager{ 55 44 clientID: clientID, 56 45 clientSecret: clientSecret, 46 + httpClient: httpClient, 57 47 } 58 48 } 59 49 ··· 76 66 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 77 67 req.SetBasicAuth(m.clientID, m.clientSecret) 78 68 79 - resp, err := http.DefaultClient.Do(req) 69 + resp, err := m.httpClient.Do(req) 80 70 if err != nil { 81 71 return "", err 82 72 } ··· 95 85 return "", fmt.Errorf("failed to get token: %s", string(body)) 96 86 } 97 87 98 - var tokenResp OAuth2TokenResponse 88 + var tokenResp oAuth2TokenResponse 99 89 if err := json.Unmarshal(body, &tokenResp); err != nil { 100 90 return "", err 101 91 } ··· 105 95 return m.token, nil 106 96 } 107 97 108 - // GetOAuth2Token fetches an access token using client credentials grant 109 - // Deprecated: use KrogerTokenManager instead 110 - func GetOAuth2Token(ctx context.Context, clientID, clientSecret string) (string, error) { 111 - tm := NewKrogerTokenManager(clientID, clientSecret) 112 - return tm.GetToken(ctx) 113 - } 114 - 115 - func FromConfig(cfg *config.Config) (*ClientWithResponses, error) { 116 - tokenManager := NewKrogerTokenManager(cfg.Kroger.ClientID, cfg.Kroger.ClientSecret) 98 + func newBearerTokenRequestEditor(cfg *config.Config, httpClient *http.Client) func(context.Context, *http.Request) error { 99 + tokenManager := NewKrogerTokenManager(cfg.Kroger.ClientID, cfg.Kroger.ClientSecret, httpClient) 117 100 118 - // Custom request editor that refreshes token if needed 119 - requestEditor := func(editorCtx context.Context, req *http.Request) error { 101 + return func(editorCtx context.Context, req *http.Request) error { 120 102 token, err := tokenManager.GetToken(editorCtx) 121 103 if err != nil { 122 104 return err ··· 124 106 req.Header.Set("Authorization", "Bearer "+token) 125 107 return nil 126 108 } 109 + } 127 110 128 - return NewClientWithResponses("https://api.kroger.com/v1", 129 - WithRequestEditorFn(requestEditor), 111 + func NewProductsClientFromConfig(cfg *config.Config, httpClient *http.Client) (*products.ClientWithResponses, error) { 112 + if httpClient == nil { 113 + httpClient = http.DefaultClient 114 + } 115 + requestEditor := newBearerTokenRequestEditor(cfg, httpClient) 116 + productsClient, err := products.NewClientWithResponses("https://api.kroger.com", 117 + products.WithHTTPClient(httpClient), 118 + products.WithRequestEditorFn(products.RequestEditorFn(requestEditor)), 130 119 ) 120 + if err != nil { 121 + return nil, fmt.Errorf("create kroger products client: %w", err) 122 + } 123 + return productsClient, nil 131 124 }
+29 -8
internal/kroger/locations.go
··· 3 3 import ( 4 4 "context" 5 5 "fmt" 6 + "net/http" 6 7 8 + "careme/internal/config" 9 + krogerlocations "careme/internal/kroger/locations" 7 10 locationtypes "careme/internal/locations/types" 8 11 ) 9 12 10 13 const chainName = "kroger" 11 14 12 - func (c *ClientWithResponses) IsID(locationID string) bool { 15 + type LocationBackend struct { 16 + client *krogerlocations.ClientWithResponses 17 + } 18 + 19 + func NewLocationBackendFromConfig(cfg *config.Config, httpClient *http.Client) (*LocationBackend, error) { 20 + if httpClient == nil { 21 + httpClient = http.DefaultClient 22 + } 23 + requestEditor := newBearerTokenRequestEditor(cfg, httpClient) 24 + locationsClient, err := krogerlocations.NewClientWithResponses("https://api.kroger.com", 25 + krogerlocations.WithHTTPClient(httpClient), 26 + krogerlocations.WithRequestEditorFn(krogerlocations.RequestEditorFn(requestEditor)), 27 + ) 28 + if err != nil { 29 + return nil, fmt.Errorf("create kroger locations client: %w", err) 30 + } 31 + return &LocationBackend{client: locationsClient}, nil 32 + } 33 + 34 + func (b *LocationBackend) IsID(locationID string) bool { 13 35 if locationID == "" { 14 36 return false 15 37 } ··· 21 43 return true 22 44 } 23 45 24 - // we should hide ClientWithResponses 25 - func (*ClientWithResponses) HasInventory(locationID string) bool { 46 + func (*LocationBackend) HasInventory(locationID string) bool { 26 47 return true 27 48 } 28 49 29 - func (c *ClientWithResponses) GetLocationByID(ctx context.Context, locationID string) (*locationtypes.Location, error) { 30 - resp, err := c.LocationDetailsWithResponse(ctx, locationID) 50 + func (b *LocationBackend) GetLocationByID(ctx context.Context, locationID string) (*locationtypes.Location, error) { 51 + resp, err := b.client.LocationsGetByIDWithResponse(ctx, locationID) 31 52 if err != nil { 32 53 return nil, err 33 54 } ··· 63 84 }, nil 64 85 } 65 86 66 - func (c *ClientWithResponses) GetLocationsByZip(ctx context.Context, zipcode string) ([]locationtypes.Location, error) { 67 - params := &LocationListParams{ 87 + func (b *LocationBackend) GetLocationsByZip(ctx context.Context, zipcode string) ([]locationtypes.Location, error) { 88 + params := &krogerlocations.SearchLocationsParams{ 68 89 FilterZipCodeNear: &zipcode, 69 90 } 70 - resp, err := c.LocationListWithResponse(ctx, params) 91 + resp, err := b.client.SearchLocationsWithResponse(ctx, params) 71 92 if err != nil { 72 93 return nil, err 73 94 }
+810
internal/kroger/locations/client.gen.go
··· 1 + // Package locations provides primitives to interact with the openapi HTTP API. 2 + // 3 + // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT. 4 + package locations 5 + 6 + import ( 7 + "context" 8 + "encoding/json" 9 + "fmt" 10 + "io" 11 + "net/http" 12 + "net/url" 13 + "strings" 14 + 15 + "github.com/oapi-codegen/runtime" 16 + ) 17 + 18 + const ( 19 + ClientContextScopes = "ClientContext.Scopes" 20 + ) 21 + 22 + // APIError defines model for APIError. 23 + type APIError struct { 24 + Code *string `json:"code,omitempty"` 25 + Reason *string `json:"reason,omitempty"` 26 + Timestamp *float32 `json:"timestamp,omitempty"` 27 + } 28 + 29 + // APIErrorLocationsServerError defines model for APIError.locations.serverError. 30 + type APIErrorLocationsServerError struct { 31 + Errors *struct { 32 + Code *string `json:"code,omitempty"` 33 + Reason *string `json:"reason,omitempty"` 34 + Timestamp *float32 `json:"timestamp,omitempty"` 35 + } `json:"errors,omitempty"` 36 + } 37 + 38 + // APIErrorNotFound defines model for APIError.notFound. 39 + type APIErrorNotFound = map[string]interface{} 40 + 41 + // APIErrorUnauthorized defines model for APIError.unauthorized. 42 + type APIErrorUnauthorized struct { 43 + Errors *struct { 44 + Error *string `json:"error,omitempty"` 45 + ErrorDescription *string `json:"error_description,omitempty"` 46 + } `json:"errors,omitempty"` 47 + } 48 + 49 + // InvalidDepartment defines model for Invalid_department. 50 + type InvalidDepartment struct { 51 + Code *string `json:"code,omitempty"` 52 + Reason *string `json:"reason,omitempty"` 53 + Timestamp *float32 `json:"timestamp,omitempty"` 54 + } 55 + 56 + // InvalidLatLong defines model for Invalid_latLong. 57 + type InvalidLatLong struct { 58 + Code *string `json:"code,omitempty"` 59 + Reason *string `json:"reason,omitempty"` 60 + Timestamp *float32 `json:"timestamp,omitempty"` 61 + } 62 + 63 + // InvalidLimit defines model for Invalid_limit. 64 + type InvalidLimit struct { 65 + Code *string `json:"code,omitempty"` 66 + Reason *string `json:"reason,omitempty"` 67 + Timestamp *float32 `json:"timestamp,omitempty"` 68 + } 69 + 70 + // InvalidLocationId defines model for Invalid_locationId. 71 + type InvalidLocationId struct { 72 + Code *string `json:"code,omitempty"` 73 + Reason *string `json:"reason,omitempty"` 74 + Timestamp *float32 `json:"timestamp,omitempty"` 75 + } 76 + 77 + // InvalidRadiusInMiles defines model for Invalid_radiusInMiles. 78 + type InvalidRadiusInMiles struct { 79 + Code *string `json:"code,omitempty"` 80 + Reason *string `json:"reason,omitempty"` 81 + Timestamp *float32 `json:"timestamp,omitempty"` 82 + } 83 + 84 + // InvalidZipCode defines model for Invalid_zipCode. 85 + type InvalidZipCode struct { 86 + Code *string `json:"code,omitempty"` 87 + Reason *string `json:"reason,omitempty"` 88 + Timestamp *float32 `json:"timestamp,omitempty"` 89 + } 90 + 91 + // MetaModel defines model for MetaModel. 92 + type MetaModel struct { 93 + Pagination *struct { 94 + Limit *float32 `json:"limit,omitempty"` 95 + Start *float32 `json:"start,omitempty"` 96 + Total *float32 `json:"total,omitempty"` 97 + } `json:"pagination,omitempty"` 98 + Warnings *[]string `json:"warnings,omitempty"` 99 + } 100 + 101 + // LocationsAddress defines model for locations.address. 102 + type LocationsAddress struct { 103 + // AddressLine1 The street address of the location. 104 + AddressLine1 *string `json:"addressLine1,omitempty"` 105 + 106 + // AddressLine2 An optional secondary address of the location. 107 + AddressLine2 *string `json:"addressLine2,omitempty"` 108 + 109 + // City The city of the location. 110 + City *string `json:"city,omitempty"` 111 + 112 + // County The county of the location. 113 + County *string `json:"county,omitempty"` 114 + 115 + // State The state or province of the location. US locations use a two-letter codes for states. 116 + State *string `json:"state,omitempty"` 117 + 118 + // ZipCode The postal code of the location. 119 + ZipCode *string `json:"zipCode,omitempty"` 120 + } 121 + 122 + // LocationsDailyHours defines model for locations.dailyHours. 123 + type LocationsDailyHours struct { 124 + // Close The time the department or location closes. 125 + Close *string `json:"close,omitempty"` 126 + 127 + // Open The time the department or location opens. 128 + Open *string `json:"open,omitempty"` 129 + 130 + // Open24 Indicates if the location or department is open 24 hours. 131 + Open24 *bool `json:"open24,omitempty"` 132 + } 133 + 134 + // LocationsDepartmentAtLocation defines model for locations.departmentAtLocation. 135 + type LocationsDepartmentAtLocation struct { 136 + // DepartmentId The 2-digit department code. 137 + DepartmentId *string `json:"departmentId,omitempty"` 138 + Hours *LocationsDepartmentHours `json:"hours,omitempty"` 139 + 140 + // Name The name of the department. 141 + Name *string `json:"name,omitempty"` 142 + 143 + // Phone The phone number of the department. 144 + Phone *string `json:"phone,omitempty"` 145 + } 146 + 147 + // LocationsDepartmentHours defines model for locations.departmentHours. 148 + type LocationsDepartmentHours struct { 149 + // Open24 Indicates if the location is open 24 hours. 150 + Open24 *bool `json:"Open24,omitempty"` 151 + Friday *LocationsDailyHours `json:"friday,omitempty"` 152 + Monday *LocationsDailyHours `json:"monday,omitempty"` 153 + Saturday *LocationsDailyHours `json:"saturday,omitempty"` 154 + Sunday *LocationsDailyHours `json:"sunday,omitempty"` 155 + Thursday *LocationsDailyHours `json:"thursday,omitempty"` 156 + Tuesday *LocationsDailyHours `json:"tuesday,omitempty"` 157 + Wednesday *LocationsDailyHours `json:"wednesday,omitempty"` 158 + } 159 + 160 + // LocationsGeoLocation defines model for locations.geoLocation. 161 + type LocationsGeoLocation struct { 162 + // LatLng The latitude and longitude of the location, comma separated 163 + LatLng *string `json:"latLng,omitempty"` 164 + 165 + // Latitude The latitude of the location 166 + Latitude *float32 `json:"latitude,omitempty"` 167 + 168 + // Longitude The longitude of the location 169 + Longitude *float32 `json:"longitude,omitempty"` 170 + } 171 + 172 + // LocationsLocation defines model for locations.location. 173 + type LocationsLocation struct { 174 + Address *LocationsAddress `json:"address,omitempty"` 175 + 176 + // Chain The name of the chain. 177 + Chain *string `json:"chain,omitempty"` 178 + 179 + // Departments The available departments at the location. 180 + Departments *[]LocationsDepartmentAtLocation `json:"departments,omitempty"` 181 + 182 + // DivisionNumber The 3-digit management division number. 183 + DivisionNumber *string `json:"divisionNumber,omitempty"` 184 + Geolocation *LocationsGeoLocation `json:"geolocation,omitempty"` 185 + Hours *LocationsLocationHours `json:"hours,omitempty"` 186 + 187 + // LocationId The 3-digit management division number followed by the 5-digit store number. 188 + LocationId *string `json:"locationId,omitempty"` 189 + 190 + // Name The name of the location. The name generally consists of the chain followed by a vanity name. 191 + Name *string `json:"name,omitempty"` 192 + 193 + // Phone The phone number of the location. 194 + Phone *string `json:"phone,omitempty"` 195 + 196 + // StoreNumber The 5-digit store number. 197 + StoreNumber *string `json:"storeNumber,omitempty"` 198 + } 199 + 200 + // LocationsLocationHours defines model for locations.locationHours. 201 + type LocationsLocationHours struct { 202 + // Open24 Indicates if the location is open 24 hours. 203 + Open24 *bool `json:"Open24,omitempty"` 204 + Friday *LocationsDailyHours `json:"friday,omitempty"` 205 + 206 + // GmtOffset The utc timezone offset from gmt. 207 + GmtOffset *string `json:"gmtOffset,omitempty"` 208 + Monday *LocationsDailyHours `json:"monday,omitempty"` 209 + Saturday *LocationsDailyHours `json:"saturday,omitempty"` 210 + Sunday *LocationsDailyHours `json:"sunday,omitempty"` 211 + Thursday *LocationsDailyHours `json:"thursday,omitempty"` 212 + 213 + // Timezone The timezone of the location. 214 + Timezone *string `json:"timezone,omitempty"` 215 + Tuesday *LocationsDailyHours `json:"tuesday,omitempty"` 216 + Wednesday *LocationsDailyHours `json:"wednesday,omitempty"` 217 + } 218 + 219 + // LocationsLocationResponse defines model for locations.locationResponse. 220 + type LocationsLocationResponse struct { 221 + Data *LocationsLocation `json:"data,omitempty"` 222 + Meta *MetaModel `json:"meta,omitempty"` 223 + } 224 + 225 + // LocationsLocationSearchResponse defines model for locations.locationSearchResponse. 226 + type LocationsLocationSearchResponse struct { 227 + Data *[]LocationsLocation `json:"data,omitempty"` 228 + Meta *MetaModel `json:"meta,omitempty"` 229 + } 230 + 231 + // SearchLocationsParams defines parameters for SearchLocations. 232 + type SearchLocationsParams struct { 233 + // FilterZipCodeNear The zip code to use as a starting point for results. 234 + FilterZipCodeNear *string `form:"filter.zipCode.near,omitempty" json:"filter.zipCode.near,omitempty"` 235 + 236 + // FilterLatLongNear The latitude and longitude to use as a starting point for results. 237 + FilterLatLongNear *string `form:"filter.latLong.near,omitempty" json:"filter.latLong.near,omitempty"` 238 + 239 + // FilterLatNear The latitude to use as a starting point for results. 240 + FilterLatNear *string `form:"filter.lat.near,omitempty" json:"filter.lat.near,omitempty"` 241 + 242 + // FilterLonNear The longitude to use as a starting point for results. 243 + FilterLonNear *string `form:"filter.lon.near,omitempty" json:"filter.lon.near,omitempty"` 244 + 245 + // FilterRadiusInMiles The mile radius of results. This will be ignored if you do not use one of the 3 starting point filters (zipCode, latLong, or lat and lon) 246 + FilterRadiusInMiles *int `form:"filter.radiusInMiles,omitempty" json:"filter.radiusInMiles,omitempty"` 247 + 248 + // FilterLimit The number of results to return. 249 + FilterLimit *int `form:"filter.limit,omitempty" json:"filter.limit,omitempty"` 250 + 251 + // FilterChain The chain name of the chain. When using this filter, only stores matching the provided chain name are returned. 252 + FilterChain *string `form:"filter.chain,omitempty" json:"filter.chain,omitempty"` 253 + 254 + // FilterDepartment The departmentId of the department. Lists must be comma-separated. When using this filter, only stores that have all of the departments provided are returned. 255 + FilterDepartment *string `form:"filter.department,omitempty" json:"filter.department,omitempty"` 256 + 257 + // FilterLocationId Comma-separated list of locationIds. 258 + FilterLocationId *string `form:"filter.locationId,omitempty" json:"filter.locationId,omitempty"` 259 + } 260 + 261 + // RequestEditorFn is the function signature for the RequestEditor callback function 262 + type RequestEditorFn func(ctx context.Context, req *http.Request) error 263 + 264 + // Doer performs HTTP requests. 265 + // 266 + // The standard http.Client implements this interface. 267 + type HttpRequestDoer interface { 268 + Do(req *http.Request) (*http.Response, error) 269 + } 270 + 271 + // Client which conforms to the OpenAPI3 specification for this service. 272 + type Client struct { 273 + // The endpoint of the server conforming to this interface, with scheme, 274 + // https://api.deepmap.com for example. This can contain a path relative 275 + // to the server, such as https://api.deepmap.com/dev-test, and all the 276 + // paths in the swagger spec will be appended to the server. 277 + Server string 278 + 279 + // Doer for performing requests, typically a *http.Client with any 280 + // customized settings, such as certificate chains. 281 + Client HttpRequestDoer 282 + 283 + // A list of callbacks for modifying requests which are generated before sending over 284 + // the network. 285 + RequestEditors []RequestEditorFn 286 + } 287 + 288 + // ClientOption allows setting custom parameters during construction 289 + type ClientOption func(*Client) error 290 + 291 + // Creates a new Client, with reasonable defaults 292 + func NewClient(server string, opts ...ClientOption) (*Client, error) { 293 + // create a client with sane default values 294 + client := Client{ 295 + Server: server, 296 + } 297 + // mutate client and add all optional params 298 + for _, o := range opts { 299 + if err := o(&client); err != nil { 300 + return nil, err 301 + } 302 + } 303 + // ensure the server URL always has a trailing slash 304 + if !strings.HasSuffix(client.Server, "/") { 305 + client.Server += "/" 306 + } 307 + // create httpClient, if not already present 308 + if client.Client == nil { 309 + client.Client = &http.Client{} 310 + } 311 + return &client, nil 312 + } 313 + 314 + // WithHTTPClient allows overriding the default Doer, which is 315 + // automatically created using http.Client. This is useful for tests. 316 + func WithHTTPClient(doer HttpRequestDoer) ClientOption { 317 + return func(c *Client) error { 318 + c.Client = doer 319 + return nil 320 + } 321 + } 322 + 323 + // WithRequestEditorFn allows setting up a callback function, which will be 324 + // called right before sending the request. This can be used to mutate the request. 325 + func WithRequestEditorFn(fn RequestEditorFn) ClientOption { 326 + return func(c *Client) error { 327 + c.RequestEditors = append(c.RequestEditors, fn) 328 + return nil 329 + } 330 + } 331 + 332 + // The interface specification for the client above. 333 + type ClientInterface interface { 334 + // SearchLocations request 335 + SearchLocations(ctx context.Context, params *SearchLocationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) 336 + 337 + // LocationsGetByID request 338 + LocationsGetByID(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*http.Response, error) 339 + } 340 + 341 + func (c *Client) SearchLocations(ctx context.Context, params *SearchLocationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 342 + req, err := NewSearchLocationsRequest(c.Server, params) 343 + if err != nil { 344 + return nil, err 345 + } 346 + req = req.WithContext(ctx) 347 + if err := c.applyEditors(ctx, req, reqEditors); err != nil { 348 + return nil, err 349 + } 350 + return c.Client.Do(req) 351 + } 352 + 353 + func (c *Client) LocationsGetByID(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*http.Response, error) { 354 + req, err := NewLocationsGetByIDRequest(c.Server, locationId) 355 + if err != nil { 356 + return nil, err 357 + } 358 + req = req.WithContext(ctx) 359 + if err := c.applyEditors(ctx, req, reqEditors); err != nil { 360 + return nil, err 361 + } 362 + return c.Client.Do(req) 363 + } 364 + 365 + // NewSearchLocationsRequest generates requests for SearchLocations 366 + func NewSearchLocationsRequest(server string, params *SearchLocationsParams) (*http.Request, error) { 367 + var err error 368 + 369 + serverURL, err := url.Parse(server) 370 + if err != nil { 371 + return nil, err 372 + } 373 + 374 + operationPath := fmt.Sprintf("/v1/locations") 375 + if operationPath[0] == '/' { 376 + operationPath = "." + operationPath 377 + } 378 + 379 + queryURL, err := serverURL.Parse(operationPath) 380 + if err != nil { 381 + return nil, err 382 + } 383 + 384 + if params != nil { 385 + queryValues := queryURL.Query() 386 + 387 + if params.FilterZipCodeNear != nil { 388 + 389 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.zipCode.near", *params.FilterZipCodeNear, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 390 + return nil, err 391 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 392 + return nil, err 393 + } else { 394 + for k, v := range parsed { 395 + for _, v2 := range v { 396 + queryValues.Add(k, v2) 397 + } 398 + } 399 + } 400 + 401 + } 402 + 403 + if params.FilterLatLongNear != nil { 404 + 405 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.latLong.near", *params.FilterLatLongNear, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 406 + return nil, err 407 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 408 + return nil, err 409 + } else { 410 + for k, v := range parsed { 411 + for _, v2 := range v { 412 + queryValues.Add(k, v2) 413 + } 414 + } 415 + } 416 + 417 + } 418 + 419 + if params.FilterLatNear != nil { 420 + 421 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.lat.near", *params.FilterLatNear, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 422 + return nil, err 423 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 424 + return nil, err 425 + } else { 426 + for k, v := range parsed { 427 + for _, v2 := range v { 428 + queryValues.Add(k, v2) 429 + } 430 + } 431 + } 432 + 433 + } 434 + 435 + if params.FilterLonNear != nil { 436 + 437 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.lon.near", *params.FilterLonNear, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 438 + return nil, err 439 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 440 + return nil, err 441 + } else { 442 + for k, v := range parsed { 443 + for _, v2 := range v { 444 + queryValues.Add(k, v2) 445 + } 446 + } 447 + } 448 + 449 + } 450 + 451 + if params.FilterRadiusInMiles != nil { 452 + 453 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.radiusInMiles", *params.FilterRadiusInMiles, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { 454 + return nil, err 455 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 456 + return nil, err 457 + } else { 458 + for k, v := range parsed { 459 + for _, v2 := range v { 460 + queryValues.Add(k, v2) 461 + } 462 + } 463 + } 464 + 465 + } 466 + 467 + if params.FilterLimit != nil { 468 + 469 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.limit", *params.FilterLimit, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { 470 + return nil, err 471 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 472 + return nil, err 473 + } else { 474 + for k, v := range parsed { 475 + for _, v2 := range v { 476 + queryValues.Add(k, v2) 477 + } 478 + } 479 + } 480 + 481 + } 482 + 483 + if params.FilterChain != nil { 484 + 485 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.chain", *params.FilterChain, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 486 + return nil, err 487 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 488 + return nil, err 489 + } else { 490 + for k, v := range parsed { 491 + for _, v2 := range v { 492 + queryValues.Add(k, v2) 493 + } 494 + } 495 + } 496 + 497 + } 498 + 499 + if params.FilterDepartment != nil { 500 + 501 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.department", *params.FilterDepartment, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 502 + return nil, err 503 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 504 + return nil, err 505 + } else { 506 + for k, v := range parsed { 507 + for _, v2 := range v { 508 + queryValues.Add(k, v2) 509 + } 510 + } 511 + } 512 + 513 + } 514 + 515 + if params.FilterLocationId != nil { 516 + 517 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.locationId", *params.FilterLocationId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 518 + return nil, err 519 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 520 + return nil, err 521 + } else { 522 + for k, v := range parsed { 523 + for _, v2 := range v { 524 + queryValues.Add(k, v2) 525 + } 526 + } 527 + } 528 + 529 + } 530 + 531 + queryURL.RawQuery = queryValues.Encode() 532 + } 533 + 534 + req, err := http.NewRequest("GET", queryURL.String(), nil) 535 + if err != nil { 536 + return nil, err 537 + } 538 + 539 + return req, nil 540 + } 541 + 542 + // NewLocationsGetByIDRequest generates requests for LocationsGetByID 543 + func NewLocationsGetByIDRequest(server string, locationId string) (*http.Request, error) { 544 + var err error 545 + 546 + var pathParam0 string 547 + 548 + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "locationId", locationId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) 549 + if err != nil { 550 + return nil, err 551 + } 552 + 553 + serverURL, err := url.Parse(server) 554 + if err != nil { 555 + return nil, err 556 + } 557 + 558 + operationPath := fmt.Sprintf("/v1/locations/%s", pathParam0) 559 + if operationPath[0] == '/' { 560 + operationPath = "." + operationPath 561 + } 562 + 563 + queryURL, err := serverURL.Parse(operationPath) 564 + if err != nil { 565 + return nil, err 566 + } 567 + 568 + req, err := http.NewRequest("GET", queryURL.String(), nil) 569 + if err != nil { 570 + return nil, err 571 + } 572 + 573 + return req, nil 574 + } 575 + 576 + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { 577 + for _, r := range c.RequestEditors { 578 + if err := r(ctx, req); err != nil { 579 + return err 580 + } 581 + } 582 + for _, r := range additionalEditors { 583 + if err := r(ctx, req); err != nil { 584 + return err 585 + } 586 + } 587 + return nil 588 + } 589 + 590 + // ClientWithResponses builds on ClientInterface to offer response payloads 591 + type ClientWithResponses struct { 592 + ClientInterface 593 + } 594 + 595 + // NewClientWithResponses creates a new ClientWithResponses, which wraps 596 + // Client with return type handling 597 + func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { 598 + client, err := NewClient(server, opts...) 599 + if err != nil { 600 + return nil, err 601 + } 602 + return &ClientWithResponses{client}, nil 603 + } 604 + 605 + // WithBaseURL overrides the baseURL. 606 + func WithBaseURL(baseURL string) ClientOption { 607 + return func(c *Client) error { 608 + newBaseURL, err := url.Parse(baseURL) 609 + if err != nil { 610 + return err 611 + } 612 + c.Server = newBaseURL.String() 613 + return nil 614 + } 615 + } 616 + 617 + // ClientWithResponsesInterface is the interface specification for the client with responses above. 618 + type ClientWithResponsesInterface interface { 619 + // SearchLocationsWithResponse request 620 + SearchLocationsWithResponse(ctx context.Context, params *SearchLocationsParams, reqEditors ...RequestEditorFn) (*SearchLocationsResponse, error) 621 + 622 + // LocationsGetByIDWithResponse request 623 + LocationsGetByIDWithResponse(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*LocationsGetByIDResponse, error) 624 + } 625 + 626 + type SearchLocationsResponse struct { 627 + Body []byte 628 + HTTPResponse *http.Response 629 + JSON200 *LocationsLocationSearchResponse 630 + JSON400 *struct { 631 + union json.RawMessage 632 + } 633 + JSON401 *APIErrorUnauthorized 634 + JSON404 *APIErrorNotFound 635 + JSON500 *APIErrorLocationsServerError 636 + } 637 + 638 + // Status returns HTTPResponse.Status 639 + func (r SearchLocationsResponse) Status() string { 640 + if r.HTTPResponse != nil { 641 + return r.HTTPResponse.Status 642 + } 643 + return http.StatusText(0) 644 + } 645 + 646 + // StatusCode returns HTTPResponse.StatusCode 647 + func (r SearchLocationsResponse) StatusCode() int { 648 + if r.HTTPResponse != nil { 649 + return r.HTTPResponse.StatusCode 650 + } 651 + return 0 652 + } 653 + 654 + type LocationsGetByIDResponse struct { 655 + Body []byte 656 + HTTPResponse *http.Response 657 + JSON200 *LocationsLocationResponse 658 + JSON400 *struct { 659 + union json.RawMessage 660 + } 661 + JSON401 *APIErrorUnauthorized 662 + JSON404 *APIErrorNotFound 663 + JSON500 *APIErrorLocationsServerError 664 + } 665 + 666 + // Status returns HTTPResponse.Status 667 + func (r LocationsGetByIDResponse) Status() string { 668 + if r.HTTPResponse != nil { 669 + return r.HTTPResponse.Status 670 + } 671 + return http.StatusText(0) 672 + } 673 + 674 + // StatusCode returns HTTPResponse.StatusCode 675 + func (r LocationsGetByIDResponse) StatusCode() int { 676 + if r.HTTPResponse != nil { 677 + return r.HTTPResponse.StatusCode 678 + } 679 + return 0 680 + } 681 + 682 + // SearchLocationsWithResponse request returning *SearchLocationsResponse 683 + func (c *ClientWithResponses) SearchLocationsWithResponse(ctx context.Context, params *SearchLocationsParams, reqEditors ...RequestEditorFn) (*SearchLocationsResponse, error) { 684 + rsp, err := c.SearchLocations(ctx, params, reqEditors...) 685 + if err != nil { 686 + return nil, err 687 + } 688 + return ParseSearchLocationsResponse(rsp) 689 + } 690 + 691 + // LocationsGetByIDWithResponse request returning *LocationsGetByIDResponse 692 + func (c *ClientWithResponses) LocationsGetByIDWithResponse(ctx context.Context, locationId string, reqEditors ...RequestEditorFn) (*LocationsGetByIDResponse, error) { 693 + rsp, err := c.LocationsGetByID(ctx, locationId, reqEditors...) 694 + if err != nil { 695 + return nil, err 696 + } 697 + return ParseLocationsGetByIDResponse(rsp) 698 + } 699 + 700 + // ParseSearchLocationsResponse parses an HTTP response from a SearchLocationsWithResponse call 701 + func ParseSearchLocationsResponse(rsp *http.Response) (*SearchLocationsResponse, error) { 702 + bodyBytes, err := io.ReadAll(rsp.Body) 703 + defer func() { _ = rsp.Body.Close() }() 704 + if err != nil { 705 + return nil, err 706 + } 707 + 708 + response := &SearchLocationsResponse{ 709 + Body: bodyBytes, 710 + HTTPResponse: rsp, 711 + } 712 + 713 + switch { 714 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 715 + var dest LocationsLocationSearchResponse 716 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 717 + return nil, err 718 + } 719 + response.JSON200 = &dest 720 + 721 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 722 + var dest struct { 723 + union json.RawMessage 724 + } 725 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 726 + return nil, err 727 + } 728 + response.JSON400 = &dest 729 + 730 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 731 + var dest APIErrorUnauthorized 732 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 733 + return nil, err 734 + } 735 + response.JSON401 = &dest 736 + 737 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: 738 + var dest APIErrorNotFound 739 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 740 + return nil, err 741 + } 742 + response.JSON404 = &dest 743 + 744 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 745 + var dest APIErrorLocationsServerError 746 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 747 + return nil, err 748 + } 749 + response.JSON500 = &dest 750 + 751 + } 752 + 753 + return response, nil 754 + } 755 + 756 + // ParseLocationsGetByIDResponse parses an HTTP response from a LocationsGetByIDWithResponse call 757 + func ParseLocationsGetByIDResponse(rsp *http.Response) (*LocationsGetByIDResponse, error) { 758 + bodyBytes, err := io.ReadAll(rsp.Body) 759 + defer func() { _ = rsp.Body.Close() }() 760 + if err != nil { 761 + return nil, err 762 + } 763 + 764 + response := &LocationsGetByIDResponse{ 765 + Body: bodyBytes, 766 + HTTPResponse: rsp, 767 + } 768 + 769 + switch { 770 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 771 + var dest LocationsLocationResponse 772 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 773 + return nil, err 774 + } 775 + response.JSON200 = &dest 776 + 777 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 778 + var dest struct { 779 + union json.RawMessage 780 + } 781 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 782 + return nil, err 783 + } 784 + response.JSON400 = &dest 785 + 786 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 787 + var dest APIErrorUnauthorized 788 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 789 + return nil, err 790 + } 791 + response.JSON401 = &dest 792 + 793 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: 794 + var dest APIErrorNotFound 795 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 796 + return nil, err 797 + } 798 + response.JSON404 = &dest 799 + 800 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 801 + var dest APIErrorLocationsServerError 802 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 803 + return nil, err 804 + } 805 + response.JSON500 = &dest 806 + 807 + } 808 + 809 + return response, nil 810 + }
+3
internal/kroger/locations/generate.go
··· 1 + package locations 2 + 3 + //go:generate go tool oapi-codegen -config cfg.yaml -include-operation-ids SearchLocations,LocationsGetByID openapi.json
+1597
internal/kroger/locations/openapi.json
··· 1 + { 2 + "openapi": "3.0.3", 3 + "info": { 4 + "title": "Location API", 5 + "description": "The Locations API provides access to all locations, chains, and departments that are owned by The Kroger Co. <br><br>\n\n### Rate Limit\n\nThe Public Locations API has a **1,600 call a day per endpoint** rate limit. \n\nFor the Locations API, there are three endpoints that each have a 1,600 call per day rate limit. Since we enforce the rate limit by the number of calls the client makes to the endpoint and not individual operations, you can distribute the 1,600 calls across operations using the same endpoint as you see fit. <br><br>\n\n### Pagination\n\nThe Locations API does not support pagination. Since the response has a default limit of ten results, the response is always one page. You can extend the total number of results for the page using the `filter.limit` parameter (200 maximum). \n\n**Note**: The mile radius is set to a 10 mile default. If you extend the number of results using the `filter.limit` parameter, you may need to extend the mile radius using the `filter.radiusInMiles` parameter to get the correct number of results. <br><br>\n\n### API Operations\n\nThe Locations API supports the following operations: <br>\n<table>\n<tr>\n <th>Name</th>\n <th>Method</th>\n <th>Description</th>\n</tr>\n<tr>\n <td>Location list</td>\n <td>GET</td>\n <td>Returns a list of locations matching a given criteria.</td>\n</tr>\n<tr>\n <td>Location details</td>\n <td>GET</td>\n <td>Returns the details of a specific location.</td>\n</tr>\n<tr>\n <td>Location query</td>\n <td>HEAD</td>\n <td>Determines if a specific location exists.</td>\n</tr>\n<tr>\n <td>Chain list</td>\n <td>GET</td>\n <td>Returns a list of all chains owned by The Kroger Co.</td>\n</tr>\n<tr>\n <td>Chain details</td>\n <td>GET</td>\n <td>Returns the details of a specific chain.</td>\n</tr>\n<tr>\n <td>Chain query</td>\n <td>HEAD</td>\n <td>Determines if a specific chain exists.</td>\n</tr>\n<tr>\n <td>Department list</td>\n <td>GET</td>\n <td>Returns a list of all departments for a specific location.</td>\n</tr>\n<tr>\n <td>Department details</td>\n <td>GET</td>\n <td>Returns the details of a specific department.</td>\n</tr>\n<tr>\n <td>Department query</td>\n <td>HEAD</td>\n <td>Determines if a specific department exists.</td>\n</tr>\n</table>\n", 6 + "termsOfService": "https://developer.kroger.com/terms", 7 + "contact": { 8 + "name": "API Support", 9 + "email": "APISupport@kroger.com", 10 + "url": "https://developer.kroger.com" 11 + }, 12 + "version": "1.2.3" 13 + }, 14 + "servers": [ 15 + { 16 + "url": "https://api.kroger.com", 17 + "description": "Production Environment" 18 + }, 19 + { 20 + "url": "https://api-ce.kroger.com", 21 + "description": "Certification Environment" 22 + } 23 + ], 24 + "security": [ 25 + { 26 + "ClientContext": [ 27 + "any" 28 + ] 29 + } 30 + ], 31 + "paths": { 32 + "/v1/locations": { 33 + "get": { 34 + "tags": [ 35 + "Locations" 36 + ], 37 + "summary": "Location list", 38 + "description": "Provides access to a list of locations. If the parameter `filter.chain` is not provided, the results include all locations and chains owned by The Kroger Co.<br>You may include one of the following parameters to narrow search results within a geographic area:<br><br> <ul> <li> <code>filter.zipCode.near</code></li> <li> <code>filter.latLong.near</code></li> <li> <code>filter.lat.near</code> and <code>filter.lon.near</code></li> </ul>", 39 + "operationId": "SearchLocations", 40 + "parameters": [ 41 + { 42 + "name": "filter.zipCode.near", 43 + "in": "query", 44 + "description": "The zip code to use as a starting point for results.", 45 + "example": 45044, 46 + "schema": { 47 + "type": "string" 48 + } 49 + }, 50 + { 51 + "name": "filter.latLong.near", 52 + "in": "query", 53 + "description": "The latitude and longitude to use as a starting point for results.", 54 + "example": "39.306346,-84.278902", 55 + "schema": { 56 + "type": "string" 57 + } 58 + }, 59 + { 60 + "name": "filter.lat.near", 61 + "in": "query", 62 + "description": "The latitude to use as a starting point for results.", 63 + "example": 39.306346, 64 + "schema": { 65 + "type": "string" 66 + } 67 + }, 68 + { 69 + "name": "filter.lon.near", 70 + "in": "query", 71 + "description": "The longitude to use as a starting point for results.", 72 + "example": -84.278902, 73 + "schema": { 74 + "type": "string" 75 + } 76 + }, 77 + { 78 + "name": "filter.radiusInMiles", 79 + "in": "query", 80 + "description": "The mile radius of results. This will be ignored if you do not use one of the 3 starting point filters (zipCode, latLong, or lat and lon)", 81 + "schema": { 82 + "maximum": 100, 83 + "minimum": 1, 84 + "type": "integer", 85 + "default": 10 86 + } 87 + }, 88 + { 89 + "name": "filter.limit", 90 + "in": "query", 91 + "description": "The number of results to return.", 92 + "schema": { 93 + "maximum": 200, 94 + "minimum": 1, 95 + "type": "integer", 96 + "default": 10 97 + } 98 + }, 99 + { 100 + "name": "filter.chain", 101 + "in": "query", 102 + "description": "The chain name of the chain. When using this filter, only stores matching the provided chain name are returned.", 103 + "example": "Kroger", 104 + "schema": { 105 + "type": "string" 106 + } 107 + }, 108 + { 109 + "name": "filter.department", 110 + "in": "query", 111 + "description": "The departmentId of the department. Lists must be comma-separated. When using this filter, only stores that have all of the departments provided are returned.", 112 + "example": 13, 113 + "schema": { 114 + "maxLength": 2, 115 + "minLength": 2, 116 + "type": "string" 117 + } 118 + }, 119 + { 120 + "name": "filter.locationId", 121 + "in": "query", 122 + "description": "Comma-separated list of locationIds.", 123 + "example": "01400390", 124 + "schema": { 125 + "maxLength": 8, 126 + "minLength": 8, 127 + "type": "string" 128 + } 129 + } 130 + ], 131 + "responses": { 132 + "200": { 133 + "description": "OK", 134 + "content": { 135 + "application/json": { 136 + "schema": { 137 + "$ref": "#/components/schemas/locations.locationSearchResponse" 138 + } 139 + } 140 + } 141 + }, 142 + "400": { 143 + "description": "Bad Request", 144 + "content": { 145 + "application/json": { 146 + "schema": { 147 + "oneOf": [ 148 + { 149 + "$ref": "#/components/schemas/APIError" 150 + }, 151 + { 152 + "$ref": "#/components/schemas/Invalid_locationId" 153 + }, 154 + { 155 + "$ref": "#/components/schemas/Invalid_zipCode" 156 + }, 157 + { 158 + "$ref": "#/components/schemas/Invalid_latLong" 159 + }, 160 + { 161 + "$ref": "#/components/schemas/Invalid_radiusInMiles" 162 + }, 163 + { 164 + "$ref": "#/components/schemas/Invalid_limit" 165 + }, 166 + { 167 + "$ref": "#/components/schemas/Invalid_department" 168 + } 169 + ] 170 + } 171 + } 172 + } 173 + }, 174 + "401": { 175 + "description": "Unauthorized", 176 + "content": { 177 + "application/json": { 178 + "schema": { 179 + "$ref": "#/components/schemas/APIError.unauthorized" 180 + } 181 + } 182 + } 183 + }, 184 + "404": { 185 + "description": "Not Found", 186 + "content": { 187 + "application/json": { 188 + "schema": { 189 + "$ref": "#/components/schemas/APIError.notFound" 190 + } 191 + } 192 + } 193 + }, 194 + "500": { 195 + "description": "Internal Server Error", 196 + "content": { 197 + "application/json": { 198 + "schema": { 199 + "$ref": "#/components/schemas/APIError.locations.serverError" 200 + } 201 + } 202 + } 203 + } 204 + }, 205 + "security": [ 206 + { 207 + "ClientContext": [ 208 + "N/A" 209 + ] 210 + } 211 + ], 212 + "x-code-samples": [ 213 + { 214 + "lang": "Shell", 215 + "source": "curl -X GET \\\n 'https://api.kroger.com/v1/locations' \\\n -H 'Accept: application/json' \\\n -H 'Authorization: Bearer {{TOKEN}}'\n" 216 + }, 217 + { 218 + "lang": "Go", 219 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/locations\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 220 + }, 221 + { 222 + "lang": "JavaScript", 223 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/locations\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 224 + }, 225 + { 226 + "lang": "Java", 227 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/locations\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 228 + } 229 + ] 230 + } 231 + }, 232 + "/v1/locations/{locationId}": { 233 + "get": { 234 + "tags": [ 235 + "Locations" 236 + ], 237 + "summary": "Location details", 238 + "description": "Provides access to the details of a specific location by using the `locationId`.", 239 + "operationId": "LocationsGetByID", 240 + "parameters": [ 241 + { 242 + "name": "locationId", 243 + "in": "path", 244 + "description": "The locationId of the store.", 245 + "example": "01400943", 246 + "required": true, 247 + "schema": { 248 + "maxLength": 8, 249 + "minLength": 8, 250 + "type": "string" 251 + } 252 + } 253 + ], 254 + "responses": { 255 + "200": { 256 + "description": "OK", 257 + "content": { 258 + "application/json": { 259 + "schema": { 260 + "$ref": "#/components/schemas/locations.locationResponse" 261 + } 262 + } 263 + } 264 + }, 265 + "400": { 266 + "description": "Bad Request", 267 + "content": { 268 + "application/json": { 269 + "schema": { 270 + "oneOf": [ 271 + { 272 + "$ref": "#/components/schemas/APIError" 273 + }, 274 + { 275 + "$ref": "#/components/schemas/Invalid_locationId" 276 + } 277 + ] 278 + } 279 + } 280 + } 281 + }, 282 + "401": { 283 + "description": "Unauthorized", 284 + "content": { 285 + "application/json": { 286 + "schema": { 287 + "$ref": "#/components/schemas/APIError.unauthorized" 288 + } 289 + } 290 + } 291 + }, 292 + "404": { 293 + "description": "Not Found", 294 + "content": { 295 + "application/json": { 296 + "schema": { 297 + "$ref": "#/components/schemas/APIError.notFound" 298 + } 299 + } 300 + } 301 + }, 302 + "500": { 303 + "description": "Internal Server Error", 304 + "content": { 305 + "application/json": { 306 + "schema": { 307 + "$ref": "#/components/schemas/APIError.locations.serverError" 308 + } 309 + } 310 + } 311 + } 312 + }, 313 + "security": [ 314 + { 315 + "ClientContext": [ 316 + "N/A" 317 + ] 318 + } 319 + ], 320 + "x-code-samples": [ 321 + { 322 + "lang": "Shell", 323 + "source": "curl -X GET \\\\\\n 'https://api.kroger.com/v1/locations/{{LOCATION_ID}}'\n\\\\\\n -H 'Accept: application/json' \\\\\\n -H 'Authorization: Bearer {{TOKEN}}'\n\\n\n" 324 + }, 325 + { 326 + "lang": "Go", 327 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 328 + }, 329 + { 330 + "lang": "JavaScript", 331 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\",\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 332 + }, 333 + { 334 + "lang": "Java", 335 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 336 + } 337 + ] 338 + }, 339 + "head": { 340 + "tags": [ 341 + "Locations" 342 + ], 343 + "summary": "Location query", 344 + "description": "Determines if a specific location exists by using the `locationId`.", 345 + "operationId": "LocationsExistsByID", 346 + "parameters": [ 347 + { 348 + "name": "locationId", 349 + "in": "path", 350 + "description": "The locationId of the store.", 351 + "example": "01400943", 352 + "required": true, 353 + "schema": { 354 + "maxLength": 8, 355 + "minLength": 8, 356 + "type": "string" 357 + } 358 + } 359 + ], 360 + "responses": { 361 + "204": { 362 + "description": "No Content", 363 + "content": { 364 + "application/json": { 365 + "schema": { 366 + "$ref": "#/components/schemas/APIError.noContent" 367 + } 368 + } 369 + } 370 + }, 371 + "400": { 372 + "description": "incorrect locationId format", 373 + "content": { 374 + "application/json": { 375 + "schema": { 376 + "oneOf": [ 377 + { 378 + "$ref": "#/components/schemas/APIError" 379 + }, 380 + { 381 + "$ref": "#/components/schemas/Invalid_locationId" 382 + } 383 + ] 384 + } 385 + } 386 + } 387 + }, 388 + "401": { 389 + "description": "Unauthorized", 390 + "content": { 391 + "application/json": { 392 + "schema": { 393 + "$ref": "#/components/schemas/APIError.unauthorized" 394 + } 395 + } 396 + } 397 + }, 398 + "404": { 399 + "description": "Not Found", 400 + "content": { 401 + "application/json": { 402 + "schema": { 403 + "$ref": "#/components/schemas/APIError.notFound" 404 + } 405 + } 406 + } 407 + }, 408 + "500": { 409 + "description": "Internal Server Error", 410 + "content": { 411 + "application/json": { 412 + "schema": { 413 + "$ref": "#/components/schemas/APIError.locations.serverError" 414 + } 415 + } 416 + } 417 + } 418 + }, 419 + "security": [ 420 + { 421 + "ClientContext": [ 422 + "N/A" 423 + ] 424 + } 425 + ], 426 + "x-code-samples": [ 427 + { 428 + "lang": "Shell", 429 + "source": "curl -X HEAD \\\\\\n 'https://api.kroger.com/v1/locations/{{LOCATION_ID}}'\n\\\\\\n -H 'Accept: application/json' \\\\\\n -H 'Authorization: Bearer {{TOKEN}}'\n\\n\n" 430 + }, 431 + { 432 + "lang": "Go", 433 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\"\n\n req, _ := http.NewRequest(\"HEAD\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 434 + }, 435 + { 436 + "lang": "JavaScript", 437 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\",\n \"method\": \"HEAD\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\",\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 438 + }, 439 + { 440 + "lang": "Java", 441 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/locations/{{LOCATION_ID}}\")\n .head()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 442 + } 443 + ] 444 + } 445 + }, 446 + "/v1/chains": { 447 + "get": { 448 + "tags": [ 449 + "Locations" 450 + ], 451 + "summary": "Chain list", 452 + "description": "Provides access to a list of all chains owned by The Kroger Co.", 453 + "operationId": "ListChains", 454 + "responses": { 455 + "200": { 456 + "description": "OK", 457 + "content": { 458 + "application/json": { 459 + "schema": { 460 + "$ref": "#/components/schemas/locations.chainsResponse" 461 + } 462 + } 463 + } 464 + }, 465 + "401": { 466 + "description": "Unauthorized", 467 + "content": { 468 + "application/json": { 469 + "schema": { 470 + "$ref": "#/components/schemas/APIError.unauthorized" 471 + } 472 + } 473 + } 474 + }, 475 + "500": { 476 + "description": "Internal Server Error", 477 + "content": { 478 + "application/json": { 479 + "schema": { 480 + "$ref": "#/components/schemas/APIError.locations.serverError" 481 + } 482 + } 483 + } 484 + } 485 + }, 486 + "security": [ 487 + { 488 + "ClientContext": [ 489 + "N/A" 490 + ] 491 + } 492 + ], 493 + "x-code-samples": [ 494 + { 495 + "lang": "Shell", 496 + "source": "curl -X GET \\\n 'https://api.kroger.com/v1/chains' \\\n -H 'Accept: application/json' \\\n -H 'Authorization: Bearer {{TOKEN}}'\n" 497 + }, 498 + { 499 + "lang": "Go", 500 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/chains\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 501 + }, 502 + { 503 + "lang": "JavaScript", 504 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/chains\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\",\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 505 + }, 506 + { 507 + "lang": "Java", 508 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/chains\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 509 + } 510 + ] 511 + } 512 + }, 513 + "/v1/chains/{name}": { 514 + "get": { 515 + "tags": [ 516 + "Locations" 517 + ], 518 + "summary": "Chain details", 519 + "description": "Provides access to the details of a specific chian by using the chain `name`.", 520 + "operationId": "GetChain", 521 + "parameters": [ 522 + { 523 + "name": "name", 524 + "in": "path", 525 + "description": "The name of a chain owned by The Kroger Co. <br><br> **Note**:\nthe chain `name` is returned from the [/chains](#operation/ListChains) endpoint\nas `name` and from the [/locations](#operation/SearchLocations) endpoint\nas `chain`.\n", 526 + "example": "Kroger", 527 + "required": true, 528 + "schema": { 529 + "type": "string" 530 + } 531 + } 532 + ], 533 + "responses": { 534 + "200": { 535 + "description": "OK", 536 + "content": { 537 + "application/json": { 538 + "schema": { 539 + "$ref": "#/components/schemas/locations.chainResponse" 540 + } 541 + } 542 + } 543 + }, 544 + "401": { 545 + "description": "Unauthorized", 546 + "content": { 547 + "application/json": { 548 + "schema": { 549 + "$ref": "#/components/schemas/APIError.unauthorized" 550 + } 551 + } 552 + } 553 + }, 554 + "404": { 555 + "description": "Not Found", 556 + "content": { 557 + "application/json": { 558 + "schema": { 559 + "$ref": "#/components/schemas/APIError.notFound" 560 + } 561 + } 562 + } 563 + }, 564 + "500": { 565 + "description": "Internal Server Error", 566 + "content": { 567 + "application/json": { 568 + "schema": { 569 + "$ref": "#/components/schemas/APIError.locations.serverError" 570 + } 571 + } 572 + } 573 + } 574 + }, 575 + "security": [ 576 + { 577 + "ClientContext": [ 578 + "N/A" 579 + ] 580 + } 581 + ], 582 + "x-code-samples": [ 583 + { 584 + "lang": "Shell", 585 + "source": "curl -X GET \\\\\\n 'https://api.kroger.com/v1/chains/{{NAME}}' \\\\\\n\n\\ -H 'Accept: application/json' \\\\\\n -H 'Authorization: Bearer {{TOKEN}}'\n\\n\n" 586 + }, 587 + { 588 + "lang": "Go", 589 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/chains/{{NAME}}\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 590 + }, 591 + { 592 + "lang": "JavaScript", 593 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/chains/{{NAME}}\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 594 + }, 595 + { 596 + "lang": "Java", 597 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/chains/{{NAME}}\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 598 + } 599 + ] 600 + }, 601 + "head": { 602 + "tags": [ 603 + "Locations" 604 + ], 605 + "summary": "Chain query", 606 + "description": "Determine if a specific chain exists by using the chain `name`.", 607 + "operationId": "ChainExists", 608 + "parameters": [ 609 + { 610 + "name": "name", 611 + "in": "path", 612 + "description": "The name of a chain owned by The Kroger Co. <br><br> **Note**:\nthe chain `name` is returned from the [/chains](#operation/ListChains) endpoint\nas `name` and from the [/locations](#operation/SearchLocations) endpoint\nas `chain`.\n", 613 + "example": "Kroger", 614 + "required": true, 615 + "schema": { 616 + "type": "string" 617 + } 618 + } 619 + ], 620 + "responses": { 621 + "204": { 622 + "description": "No Content", 623 + "content": { 624 + "application/json": { 625 + "schema": { 626 + "$ref": "#/components/schemas/APIError.noContent" 627 + } 628 + } 629 + } 630 + }, 631 + "401": { 632 + "description": "Unauthorized", 633 + "content": { 634 + "application/json": { 635 + "schema": { 636 + "$ref": "#/components/schemas/APIError.unauthorized" 637 + } 638 + } 639 + } 640 + }, 641 + "404": { 642 + "description": "Not Found", 643 + "content": { 644 + "application/json": { 645 + "schema": { 646 + "$ref": "#/components/schemas/APIError.notFound" 647 + } 648 + } 649 + } 650 + }, 651 + "500": { 652 + "description": "Internal Server Error", 653 + "content": { 654 + "application/json": { 655 + "schema": { 656 + "$ref": "#/components/schemas/APIError.locations.serverError" 657 + } 658 + } 659 + } 660 + } 661 + }, 662 + "security": [ 663 + { 664 + "ClientContext": [ 665 + "N/A" 666 + ] 667 + } 668 + ], 669 + "x-code-samples": [ 670 + { 671 + "lang": "Shell", 672 + "source": "curl -X HEAD \\\\\\n 'https://api.kroger.com/v1/chains/{{NAME}}' \\\\\\n\n\\ -H 'Accept: application/json' \\\\\\n -H 'Authorization: Bearer {{TOKEN}}'\n\\n\n" 673 + }, 674 + { 675 + "lang": "Go", 676 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/chains/{{NAME}}\"\n\n req, _ := http.NewRequest(\"HEAD\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 677 + }, 678 + { 679 + "lang": "JavaScript", 680 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/chains/{{NAME}}\",\n \"method\": \"HEAD\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 681 + }, 682 + { 683 + "lang": "Java", 684 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/chains/{{NAME}}\")\n .head()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 685 + } 686 + ] 687 + } 688 + }, 689 + "/v1/departments": { 690 + "get": { 691 + "tags": [ 692 + "Locations" 693 + ], 694 + "summary": "Department list", 695 + "description": "Provides access to a list of all departments, including departments of chains owned by The Kroger Co.", 696 + "operationId": "ListDepartments", 697 + "responses": { 698 + "200": { 699 + "description": "OK", 700 + "content": { 701 + "application/json": { 702 + "schema": { 703 + "$ref": "#/components/schemas/locations.departmentsResponse" 704 + } 705 + } 706 + } 707 + }, 708 + "400": { 709 + "description": "Bad Request", 710 + "content": { 711 + "application/json": { 712 + "schema": { 713 + "$ref": "#/components/schemas/APIError" 714 + } 715 + } 716 + } 717 + }, 718 + "401": { 719 + "description": "Unauthorized", 720 + "content": { 721 + "application/json": { 722 + "schema": { 723 + "$ref": "#/components/schemas/APIError.unauthorized" 724 + } 725 + } 726 + } 727 + }, 728 + "500": { 729 + "description": "Internal Server Error", 730 + "content": { 731 + "application/json": { 732 + "schema": { 733 + "$ref": "#/components/schemas/APIError.locations.serverError" 734 + } 735 + } 736 + } 737 + } 738 + }, 739 + "security": [ 740 + { 741 + "ClientContext": [ 742 + "N/A" 743 + ] 744 + } 745 + ], 746 + "x-code-samples": [ 747 + { 748 + "lang": "Shell", 749 + "source": "curl -X GET \\\n 'https://api.kroger.com/v1/departments' \\\n -H 'Accept: application/json' \\\n -H 'Authorization: Bearer {{TOKEN}}'\n" 750 + }, 751 + { 752 + "lang": "Go", 753 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/departments\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 754 + }, 755 + { 756 + "lang": "JavaScript", 757 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/departments\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 758 + }, 759 + { 760 + "lang": "Java", 761 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/departments\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"X-Correlation-ID\", \"4cbcad8a-597e-4d42-a9f7-c88e53f430db\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 762 + } 763 + ] 764 + } 765 + }, 766 + "/v1/departments/{id}": { 767 + "get": { 768 + "tags": [ 769 + "Locations" 770 + ], 771 + "summary": "Department details", 772 + "description": "Provides access to the details of a specific department by using the `departmentId`. ", 773 + "operationId": "GetDepartment", 774 + "parameters": [ 775 + { 776 + "name": "id", 777 + "in": "path", 778 + "description": "The departmentId of the department to return.", 779 + "example": 13, 780 + "required": true, 781 + "schema": { 782 + "maxLength": 2, 783 + "minLength": 2, 784 + "type": "string" 785 + } 786 + } 787 + ], 788 + "responses": { 789 + "200": { 790 + "description": "OK", 791 + "content": { 792 + "application/json": { 793 + "schema": { 794 + "$ref": "#/components/schemas/locations.departmentResponse" 795 + } 796 + } 797 + } 798 + }, 799 + "400": { 800 + "description": "Bad Request", 801 + "content": { 802 + "application/json": { 803 + "schema": { 804 + "$ref": "#/components/schemas/APIError.departmentsId.badRequest" 805 + } 806 + } 807 + } 808 + }, 809 + "401": { 810 + "description": "Unauthorized", 811 + "content": { 812 + "application/json": { 813 + "schema": { 814 + "$ref": "#/components/schemas/APIError.unauthorized" 815 + } 816 + } 817 + } 818 + }, 819 + "404": { 820 + "description": "Not Found", 821 + "content": { 822 + "application/json": { 823 + "schema": { 824 + "$ref": "#/components/schemas/APIError.notFound" 825 + } 826 + } 827 + } 828 + }, 829 + "500": { 830 + "description": "Internal Server Error", 831 + "content": { 832 + "application/json": { 833 + "schema": { 834 + "$ref": "#/components/schemas/APIError.locations.serverError" 835 + } 836 + } 837 + } 838 + } 839 + }, 840 + "security": [ 841 + { 842 + "ClientContext": [ 843 + "N/A" 844 + ] 845 + } 846 + ], 847 + "x-code-samples": [ 848 + { 849 + "lang": "Shell", 850 + "source": "curl -X GET \\\n 'https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}' \\\n -H 'Accept: application/json' \\\n -H 'Authorization: Bearer {{TOKEN}}'\n" 851 + }, 852 + { 853 + "lang": "Go", 854 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\"\n\n req, _ := http.NewRequest(\"GET\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 855 + }, 856 + { 857 + "lang": "JavaScript", 858 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\",\n \"method\": \"GET\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 859 + }, 860 + { 861 + "lang": "Java", 862 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\")\n .get()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 863 + } 864 + ] 865 + }, 866 + "head": { 867 + "tags": [ 868 + "Locations" 869 + ], 870 + "summary": "Department query", 871 + "description": "Determine if a specific department exists by using the `departmentId`. ", 872 + "operationId": "DepartmentExists", 873 + "parameters": [ 874 + { 875 + "name": "id", 876 + "in": "path", 877 + "description": "The departmentId of the department to return.", 878 + "example": 13, 879 + "required": true, 880 + "schema": { 881 + "maxLength": 2, 882 + "minLength": 2, 883 + "type": "string" 884 + } 885 + } 886 + ], 887 + "responses": { 888 + "204": { 889 + "description": "No Content", 890 + "content": { 891 + "application/json": { 892 + "schema": { 893 + "$ref": "#/components/schemas/APIError.noContent" 894 + } 895 + } 896 + } 897 + }, 898 + "400": { 899 + "description": "Bad Request", 900 + "content": { 901 + "application/json": { 902 + "schema": { 903 + "$ref": "#/components/schemas/APIError.departmentsId.badRequest" 904 + } 905 + } 906 + } 907 + }, 908 + "401": { 909 + "description": "Unauthorized", 910 + "content": { 911 + "application/json": { 912 + "schema": { 913 + "$ref": "#/components/schemas/APIError.unauthorized" 914 + } 915 + } 916 + } 917 + }, 918 + "404": { 919 + "description": "Not Found", 920 + "content": { 921 + "application/json": { 922 + "schema": { 923 + "$ref": "#/components/schemas/APIError.notFound" 924 + } 925 + } 926 + } 927 + }, 928 + "500": { 929 + "description": "Internal Server Error", 930 + "content": { 931 + "application/json": { 932 + "schema": { 933 + "$ref": "#/components/schemas/APIError.locations.serverError" 934 + } 935 + } 936 + } 937 + } 938 + }, 939 + "security": [ 940 + { 941 + "ClientContext": [ 942 + "N/A" 943 + ] 944 + } 945 + ], 946 + "x-code-samples": [ 947 + { 948 + "lang": "Shell", 949 + "source": "curl -X HEAD \\\n 'https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}' \\\n -H 'Accept: application/json' \\\n -H 'Authorization: Bearer {{TOKEN}}'\n" 950 + }, 951 + { 952 + "lang": "Go", 953 + "source": "package main\n\nimport (\n \"fmt\"\n \"net/http\"\n \"io/ioutil\"\n)\n\nfunc main() {\n\n url := \"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\"\n\n req, _ := http.NewRequest(\"HEAD\", url, nil)\n\n req.Header.Add(\"Accept\", \"application/json\")\n req.Header.Add(\"Authorization\", \"Bearer {{TOKEN}}\")\n\n res, _ := http.DefaultClient.Do(req)\n\n defer res.Body.Close()\n body, _ := ioutil.ReadAll(res.Body)\n\n fmt.Println(res)\n fmt.Println(string(body))\n\n}\n" 954 + }, 955 + { 956 + "lang": "JavaScript", 957 + "source": "var settings = {\n \"async\": true,\n \"crossDomain\": true,\n \"url\": \"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\",\n \"method\": \"HEAD\",\n \"headers\": {\n \"Accept\": \"application/json\",\n \"Authorization\": \"Bearer {{TOKEN}}\"\n }\n}\n\n$.ajax(settings).done(function (response) {\n console.log(response);\n});\n" 958 + }, 959 + { 960 + "lang": "Java", 961 + "source": "OkHttpClient client = new OkHttpClient();\n\nRequest request = new Request.Builder()\n .url(\"https://api.kroger.com/v1/departments/{{DEPARTMENT_ID}}\")\n .head()\n .addHeader(\"Accept\", \"application/json\")\n .addHeader(\"Authorization\", \"Bearer {{TOKEN}}\")\n .build();\n\nResponse response = client.newCall(request).execute();\n" 962 + } 963 + ] 964 + } 965 + } 966 + }, 967 + "components": { 968 + "schemas": { 969 + "APIError": { 970 + "type": "object", 971 + "properties": { 972 + "timestamp": { 973 + "type": "number" 974 + }, 975 + "code": { 976 + "type": "string" 977 + }, 978 + "reason": { 979 + "type": "string" 980 + } 981 + } 982 + }, 983 + "APIError.departmentsId.badRequest": { 984 + "type": "object", 985 + "properties": { 986 + "errors": { 987 + "type": "object", 988 + "properties": { 989 + "reason": { 990 + "type": "string", 991 + "example": "Invalid department ID" 992 + }, 993 + "code": { 994 + "type": "string", 995 + "example": "LOCATION-2119" 996 + }, 997 + "timestamp": { 998 + "type": "number", 999 + "example": 1564159296910 1000 + } 1001 + } 1002 + } 1003 + } 1004 + }, 1005 + "APIError.unauthorized": { 1006 + "type": "object", 1007 + "properties": { 1008 + "errors": { 1009 + "type": "object", 1010 + "properties": { 1011 + "error_description": { 1012 + "type": "string", 1013 + "example": "The access token is invalid or has expired" 1014 + }, 1015 + "error": { 1016 + "type": "string", 1017 + "example": "invalid_token" 1018 + } 1019 + } 1020 + } 1021 + } 1022 + }, 1023 + "APIError.locations.serverError": { 1024 + "type": "object", 1025 + "properties": { 1026 + "errors": { 1027 + "type": "object", 1028 + "properties": { 1029 + "reason": { 1030 + "type": "string", 1031 + "example": "Internal server error" 1032 + }, 1033 + "code": { 1034 + "type": "string", 1035 + "example": "LOCATION-4xxx-xxx" 1036 + }, 1037 + "timestamp": { 1038 + "type": "number", 1039 + "example": 1564159296910 1040 + } 1041 + } 1042 + } 1043 + } 1044 + }, 1045 + "APIError.noContent": { 1046 + "type": "object" 1047 + }, 1048 + "APIError.notFound": { 1049 + "type": "object" 1050 + }, 1051 + "locations.address": { 1052 + "type": "object", 1053 + "properties": { 1054 + "addressLine1": { 1055 + "type": "string", 1056 + "description": "The street address of the location.", 1057 + "example": "2900 W. St. Rt. 22 & 3" 1058 + }, 1059 + "addressLine2": { 1060 + "type": "string", 1061 + "description": "An optional secondary address of the location.", 1062 + "example": "" 1063 + }, 1064 + "city": { 1065 + "type": "string", 1066 + "description": "The city of the location.", 1067 + "example": "Maineville" 1068 + }, 1069 + "county": { 1070 + "type": "string", 1071 + "description": "The county of the location.", 1072 + "example": "WARREN COUNTY" 1073 + }, 1074 + "state": { 1075 + "type": "string", 1076 + "description": "The state or province of the location. US locations use a two-letter codes for states.", 1077 + "example": "OH" 1078 + }, 1079 + "zipCode": { 1080 + "type": "string", 1081 + "description": "The postal code of the location.", 1082 + "example": "45039" 1083 + } 1084 + } 1085 + }, 1086 + "locations.chain": { 1087 + "type": "object", 1088 + "properties": { 1089 + "name": { 1090 + "type": "string", 1091 + "description": "The name of the chain.", 1092 + "example": "KROGER" 1093 + }, 1094 + "divisionNumbers": { 1095 + "type": "array", 1096 + "description": "A list of all management division numbers within the brand.", 1097 + "example": [ 1098 + "01", 1099 + "02" 1100 + ], 1101 + "items": { 1102 + "type": "string" 1103 + } 1104 + }, 1105 + "domain": { 1106 + "type": "string", 1107 + "description": "The domain of the chain.", 1108 + "example": "kroger.com" 1109 + }, 1110 + "friendlyBannerName": { 1111 + "type": "string", 1112 + "description": "The friendly name for this chain.", 1113 + "example": "Baker's" 1114 + }, 1115 + "defaultTitle": { 1116 + "type": "string", 1117 + "description": "The default title for this chain.", 1118 + "example": "Baker's" 1119 + }, 1120 + "titleExtension": { 1121 + "type": "string", 1122 + "description": "The title extension for this chain.", 1123 + "example": "Fresh Food. Low Prices. | Shop Groceries Online" 1124 + }, 1125 + "appleAppId": { 1126 + "type": "string", 1127 + "description": "The Apple app ID for this chain.", 1128 + "example": "app-id=584405207" 1129 + }, 1130 + "googleAppId": { 1131 + "type": "string", 1132 + "description": "The Google app ID for this chain.", 1133 + "example": "app-id=com.bakersplus.mobile" 1134 + }, 1135 + "themeColor": { 1136 + "type": "string", 1137 + "description": "The theme color for this chain.", 1138 + "example": "#ef3e42" 1139 + }, 1140 + "description": { 1141 + "type": "string", 1142 + "description": "The description for this chain.", 1143 + "example": "Shop low prices on groceries & choose pickup or delivery. Fill prescriptions, save with 100s of digital coupons, get fuel points, cash checks, send money & more at Baker's." 1144 + }, 1145 + "modalityCapabilities": { 1146 + "type": "object", 1147 + "description": "The e-commerce modality capabilities for this chain.", 1148 + "properties": { 1149 + "delivery": { 1150 + "type": "boolean", 1151 + "description": "Flag for if this chain supports e-commerce delivery." 1152 + }, 1153 + "instore": { 1154 + "type": "boolean", 1155 + "description": "Flag for if this chain supports e-commerce in-store shopping." 1156 + }, 1157 + "curbside": { 1158 + "type": "boolean", 1159 + "description": "Flag for if this chain supports e-commerce pickup." 1160 + }, 1161 + "shiptohome": { 1162 + "type": "boolean", 1163 + "description": "Flag for if this chain supports e-commerce shipping." 1164 + } 1165 + } 1166 + } 1167 + } 1168 + }, 1169 + "locations.chainResponse": { 1170 + "type": "object", 1171 + "properties": { 1172 + "data": { 1173 + "$ref": "#/components/schemas/locations.chain" 1174 + }, 1175 + "meta": { 1176 + "type": "object", 1177 + "properties": {} 1178 + } 1179 + } 1180 + }, 1181 + "locations.chainsResponse": { 1182 + "type": "object", 1183 + "properties": { 1184 + "data": { 1185 + "type": "array", 1186 + "items": { 1187 + "$ref": "#/components/schemas/locations.chain" 1188 + } 1189 + }, 1190 + "meta": { 1191 + "$ref": "#/components/schemas/MetaModel" 1192 + } 1193 + } 1194 + }, 1195 + "locations.dailyHours": { 1196 + "type": "object", 1197 + "properties": { 1198 + "open": { 1199 + "type": "string", 1200 + "description": "The time the department or location opens.", 1201 + "example": "05:00" 1202 + }, 1203 + "close": { 1204 + "type": "string", 1205 + "description": "The time the department or location closes.", 1206 + "example": 1380 1207 + }, 1208 + "open24": { 1209 + "type": "boolean", 1210 + "description": "Indicates if the location or department is open 24 hours.", 1211 + "example": false 1212 + } 1213 + } 1214 + }, 1215 + "locations.departmentAtLocation": { 1216 + "type": "object", 1217 + "properties": { 1218 + "departmentId": { 1219 + "type": "string", 1220 + "description": "The 2-digit department code.", 1221 + "example": "01" 1222 + }, 1223 + "name": { 1224 + "type": "string", 1225 + "description": "The name of the department.", 1226 + "example": "Drug & General Merchandise" 1227 + }, 1228 + "phone": { 1229 + "type": "string", 1230 + "description": "The phone number of the department.", 1231 + "example": "5551234567" 1232 + }, 1233 + "hours": { 1234 + "$ref": "#/components/schemas/locations.departmentHours" 1235 + } 1236 + } 1237 + }, 1238 + "locations.department": { 1239 + "type": "object", 1240 + "properties": { 1241 + "departmentId": { 1242 + "type": "string", 1243 + "description": "The 2-digit department code.", 1244 + "example": "01" 1245 + }, 1246 + "name": { 1247 + "type": "string", 1248 + "description": "The name of the department.", 1249 + "example": "Drug & General Merchandise" 1250 + } 1251 + } 1252 + }, 1253 + "locations.departmentResponse": { 1254 + "type": "object", 1255 + "properties": { 1256 + "data": { 1257 + "$ref": "#/components/schemas/locations.department" 1258 + }, 1259 + "meta": { 1260 + "type": "object", 1261 + "properties": {} 1262 + } 1263 + } 1264 + }, 1265 + "locations.departmentsResponse": { 1266 + "type": "object", 1267 + "properties": { 1268 + "data": { 1269 + "type": "array", 1270 + "items": { 1271 + "$ref": "#/components/schemas/locations.department" 1272 + } 1273 + }, 1274 + "meta": { 1275 + "$ref": "#/components/schemas/MetaModel" 1276 + } 1277 + } 1278 + }, 1279 + "locations.geoLocation": { 1280 + "type": "object", 1281 + "properties": { 1282 + "latLng": { 1283 + "type": "string", 1284 + "description": "The latitude and longitude of the location, comma separated", 1285 + "example": "39.3110881,-84.2751167" 1286 + }, 1287 + "latitude": { 1288 + "type": "number", 1289 + "description": "The latitude of the location", 1290 + "example": 39.3110881 1291 + }, 1292 + "longitude": { 1293 + "type": "number", 1294 + "description": "The longitude of the location", 1295 + "example": -84.2751167 1296 + } 1297 + } 1298 + }, 1299 + "locations.locationHours": { 1300 + "type": "object", 1301 + "properties": { 1302 + "Open24": { 1303 + "type": "boolean", 1304 + "description": "Indicates if the location is open 24 hours." 1305 + }, 1306 + "gmtOffset": { 1307 + "type": "string", 1308 + "description": "The utc timezone offset from gmt.", 1309 + "example": "(UTC-05:00) Eastern Time (US Canada)" 1310 + }, 1311 + "timezone": { 1312 + "type": "string", 1313 + "description": "The timezone of the location.", 1314 + "example": "America/New_York" 1315 + }, 1316 + "friday": { 1317 + "$ref": "#/components/schemas/locations.dailyHours" 1318 + }, 1319 + "monday": { 1320 + "$ref": "#/components/schemas/locations.dailyHours" 1321 + }, 1322 + "saturday": { 1323 + "$ref": "#/components/schemas/locations.dailyHours" 1324 + }, 1325 + "sunday": { 1326 + "$ref": "#/components/schemas/locations.dailyHours" 1327 + }, 1328 + "thursday": { 1329 + "$ref": "#/components/schemas/locations.dailyHours" 1330 + }, 1331 + "tuesday": { 1332 + "$ref": "#/components/schemas/locations.dailyHours" 1333 + }, 1334 + "wednesday": { 1335 + "$ref": "#/components/schemas/locations.dailyHours" 1336 + } 1337 + } 1338 + }, 1339 + "locations.departmentHours": { 1340 + "type": "object", 1341 + "properties": { 1342 + "Open24": { 1343 + "type": "boolean", 1344 + "description": "Indicates if the location is open 24 hours.", 1345 + "example": false 1346 + }, 1347 + "monday": { 1348 + "$ref": "#/components/schemas/locations.dailyHours" 1349 + }, 1350 + "tuesday": { 1351 + "$ref": "#/components/schemas/locations.dailyHours" 1352 + }, 1353 + "wednesday": { 1354 + "$ref": "#/components/schemas/locations.dailyHours" 1355 + }, 1356 + "thursday": { 1357 + "$ref": "#/components/schemas/locations.dailyHours" 1358 + }, 1359 + "friday": { 1360 + "$ref": "#/components/schemas/locations.dailyHours" 1361 + }, 1362 + "saturday": { 1363 + "$ref": "#/components/schemas/locations.dailyHours" 1364 + }, 1365 + "sunday": { 1366 + "$ref": "#/components/schemas/locations.dailyHours" 1367 + } 1368 + } 1369 + }, 1370 + "locations.location": { 1371 + "type": "object", 1372 + "properties": { 1373 + "address": { 1374 + "$ref": "#/components/schemas/locations.address" 1375 + }, 1376 + "chain": { 1377 + "type": "string", 1378 + "description": "The name of the chain.", 1379 + "example": "KROGER" 1380 + }, 1381 + "phone": { 1382 + "type": "string", 1383 + "description": "The phone number of the location.", 1384 + "example": "5551234567" 1385 + }, 1386 + "departments": { 1387 + "type": "array", 1388 + "description": "The available departments at the location.", 1389 + "items": { 1390 + "$ref": "#/components/schemas/locations.departmentAtLocation" 1391 + } 1392 + }, 1393 + "geolocation": { 1394 + "$ref": "#/components/schemas/locations.geoLocation" 1395 + }, 1396 + "hours": { 1397 + "$ref": "#/components/schemas/locations.locationHours" 1398 + }, 1399 + "locationId": { 1400 + "type": "string", 1401 + "description": "The 3-digit management division number followed by the 5-digit store number.", 1402 + "example": "01400376" 1403 + }, 1404 + "storeNumber": { 1405 + "type": "string", 1406 + "description": "The 5-digit store number.", 1407 + "example": "00376" 1408 + }, 1409 + "divisionNumber": { 1410 + "type": "string", 1411 + "description": "The 3-digit management division number.", 1412 + "example": "014" 1413 + }, 1414 + "name": { 1415 + "type": "string", 1416 + "description": "The name of the location. The name generally consists of the chain followed by a vanity name.", 1417 + "example": "Kroger Landen" 1418 + } 1419 + } 1420 + }, 1421 + "locations.locationSearchResponse": { 1422 + "type": "object", 1423 + "properties": { 1424 + "data": { 1425 + "type": "array", 1426 + "items": { 1427 + "$ref": "#/components/schemas/locations.location" 1428 + } 1429 + }, 1430 + "meta": { 1431 + "$ref": "#/components/schemas/MetaModel" 1432 + } 1433 + } 1434 + }, 1435 + "locations.locationResponse": { 1436 + "type": "object", 1437 + "properties": { 1438 + "data": { 1439 + "$ref": "#/components/schemas/locations.location" 1440 + }, 1441 + "meta": { 1442 + "$ref": "#/components/schemas/MetaModel" 1443 + } 1444 + } 1445 + }, 1446 + "MetaModel": { 1447 + "type": "object", 1448 + "properties": { 1449 + "pagination": { 1450 + "type": "object", 1451 + "properties": { 1452 + "total": { 1453 + "type": "number" 1454 + }, 1455 + "start": { 1456 + "type": "number" 1457 + }, 1458 + "limit": { 1459 + "type": "number" 1460 + } 1461 + } 1462 + }, 1463 + "warnings": { 1464 + "type": "array", 1465 + "items": { 1466 + "type": "string" 1467 + } 1468 + } 1469 + } 1470 + }, 1471 + "Invalid_locationId": { 1472 + "type": "object", 1473 + "properties": { 1474 + "timestamp": { 1475 + "type": "number", 1476 + "example": 1569851999383 1477 + }, 1478 + "code": { 1479 + "type": "string", 1480 + "example": "API-4101-400" 1481 + }, 1482 + "reason": { 1483 + "type": "string", 1484 + "example": "Field 'locationId' must have a length of 8 characters" 1485 + } 1486 + } 1487 + }, 1488 + "Invalid_zipCode": { 1489 + "type": "object", 1490 + "properties": { 1491 + "timestamp": { 1492 + "type": "number", 1493 + "example": 1569851999383 1494 + }, 1495 + "code": { 1496 + "type": "string", 1497 + "example": "API-4101-400" 1498 + }, 1499 + "reason": { 1500 + "type": "string", 1501 + "example": "Field 'filter.zipCode.near' must 5 digits" 1502 + } 1503 + } 1504 + }, 1505 + "Invalid_latLong": { 1506 + "type": "object", 1507 + "properties": { 1508 + "timestamp": { 1509 + "type": "number", 1510 + "example": 1569851999383 1511 + }, 1512 + "code": { 1513 + "type": "string", 1514 + "example": "API-4101-400" 1515 + }, 1516 + "reason": { 1517 + "type": "string", 1518 + "example": "Field 'filter.latLong.near' improper coordinate location, must be on Earth" 1519 + } 1520 + } 1521 + }, 1522 + "Invalid_radiusInMiles": { 1523 + "type": "object", 1524 + "properties": { 1525 + "timestamp": { 1526 + "type": "number", 1527 + "example": 1569851999383 1528 + }, 1529 + "code": { 1530 + "type": "string", 1531 + "example": "API-4101-400" 1532 + }, 1533 + "reason": { 1534 + "type": "string", 1535 + "example": "Field 'filter.radiusInMiles' outside of distance limits, distance range is 1 - 100 miles." 1536 + } 1537 + } 1538 + }, 1539 + "Invalid_department": { 1540 + "type": "object", 1541 + "properties": { 1542 + "timestamp": { 1543 + "type": "number", 1544 + "example": 1569851999383 1545 + }, 1546 + "code": { 1547 + "type": "string", 1548 + "example": "API-4101-400" 1549 + }, 1550 + "reason": { 1551 + "type": "string", 1552 + "example": "Field 'filter.department' contains invalid department ID(s)" 1553 + } 1554 + } 1555 + }, 1556 + "Invalid_limit": { 1557 + "type": "object", 1558 + "properties": { 1559 + "timestamp": { 1560 + "type": "number", 1561 + "example": 1569851999383 1562 + }, 1563 + "code": { 1564 + "type": "string", 1565 + "example": "API-4101-400" 1566 + }, 1567 + "reason": { 1568 + "type": "string", 1569 + "example": "Field 'limit' must be a number between 1 and 200 (inclusive)" 1570 + } 1571 + } 1572 + } 1573 + }, 1574 + "securitySchemes": { 1575 + "ClientContext": { 1576 + "type": "oauth2", 1577 + "description": "To make an API request that interacts with generalized information and does not require customer consent, use the [Client Credentials Grant Type](https://developer.kroger.com/reference/api/authorization-endpoints-public#tag/OAuth2/operation/accessToken) to authenticate your OAuth2 application.\n", 1578 + "flows": { 1579 + "clientCredentials": { 1580 + "tokenUrl": "https://api.kroger.com/v1/connect/oauth2/token", 1581 + "scopes": { 1582 + "N/A": "No scope required." 1583 + } 1584 + } 1585 + } 1586 + } 1587 + } 1588 + }, 1589 + "x-tagGroups": [ 1590 + { 1591 + "name": "API Reference", 1592 + "tags": [ 1593 + "Locations" 1594 + ] 1595 + } 1596 + ] 1597 + }
+1 -1
internal/kroger/locations_test.go
··· 9 9 func TestClientWithResponsesIsID(t *testing.T) { 10 10 t.Parallel() 11 11 12 - client := &ClientWithResponses{} 12 + client := &LocationBackend{} 13 13 tests := []struct { 14 14 id string 15 15 want bool
+348 -5
internal/kroger/opeanapi-products.json internal/kroger/products/openapi.json
··· 9 9 "email": "APISupport@kroger.com", 10 10 "url": "https://developer.kroger.com" 11 11 }, 12 - "version": "1.2.4" 12 + "version": "1.3.0" 13 13 }, 14 14 "servers": [ 15 15 { ··· 501 501 "description": "The URI of the product page.", 502 502 "example": "/p/kroger-2-reduced-fat-milk/0001111041700?cid=dis.api.tpi_products-api_20240521_b:all_c:p_t:" 503 503 }, 504 + "aliasProductIds": { 505 + "type": "array", 506 + "description": "The alias ProductId of the product to return.", 507 + "items": { 508 + "type": "string", 509 + "example": "0001111060903" 510 + } 511 + }, 504 512 "aisleLocations": { 505 513 "type": "array", 506 514 "items": { ··· 530 538 "description": "The name of the product.", 531 539 "example": "Kroger 2% Reduced Fat Milk" 532 540 }, 541 + "alcohol": { 542 + "type": "boolean", 543 + "description": "Indicates if the product contains alcohol.", 544 + "example": false 545 + }, 546 + "alcoholProof": { 547 + "type": "integer", 548 + "description": "The proof of the alcohol in the product.", 549 + "example": 0 550 + }, 551 + "ageRestriction": { 552 + "type": "boolean", 553 + "description": "Indicates if the product has an age restriction.", 554 + "example": false 555 + }, 556 + "snapEligible": { 557 + "type": "boolean", 558 + "description": "Indicates if the product is eligible for SNAP benefits.", 559 + "example": true 560 + }, 561 + "manufacturerDeclarations": { 562 + "type": "array", 563 + "description": "Any manufacturer declarations for the product.", 564 + "example": [ 565 + "Live Naturally", 566 + "Kosher", 567 + "Tree Nuts Free" 568 + ], 569 + "items": { 570 + "type": "string" 571 + } 572 + }, 573 + "sweeteningMethods": { 574 + "type": "object", 575 + "properties": { 576 + "code": { 577 + "type": "string", 578 + "example": "UNSPECIFIED" 579 + }, 580 + "name": { 581 + "type": "string", 582 + "example": "Unspecified" 583 + } 584 + } 585 + }, 586 + "allergens": { 587 + "type": "array", 588 + "items": { 589 + "$ref": "#/components/schemas/products.allergenModel" 590 + } 591 + }, 592 + "allergensDescription": { 593 + "type": "string", 594 + "description": "The description of the allergens for the product.", 595 + "example": "Free from Cereals and Their Derivatives." 596 + }, 597 + "certifiedForPassover": { 598 + "type": "boolean", 599 + "description": "Indicates if the product is certified for Passover.", 600 + "example": false 601 + }, 602 + "hypoallergenic": { 603 + "type": "boolean", 604 + "description": "Indicates if the product is hypoallergenic.", 605 + "example": false 606 + }, 607 + "nonGmo": { 608 + "type": "boolean", 609 + "description": "Indicates if the product is non-GMO.", 610 + "example": false 611 + }, 612 + "nonGmoClaimName": { 613 + "type": "string", 614 + "description": "The name of the non-GMO claim.", 615 + "example": "PRODUCT LABELED WITH NON-GMO CLAIM" 616 + }, 617 + "organicClaimName": { 618 + "type": "string", 619 + "description": "The name of the organic claim.", 620 + "example": "ORGANIC CLAIM AND PRINTED ON PACKAGE" 621 + }, 622 + "receiptDescription": { 623 + "type": "string", 624 + "description": "The description of the product to appear on the receipt.", 625 + "example": "KROGER 2% RF MILK" 626 + }, 627 + "warnings": { 628 + "type": "string", 629 + "description": "Any warnings for the product.", 630 + "example": "KEEP REFRIGERATED" 631 + }, 632 + "retstrictions": { 633 + "$ref": "#/components/schemas/products.restrictionsModel" 634 + }, 533 635 "items": { 534 636 "type": "array", 535 637 "items": { ··· 552 654 "type": "string", 553 655 "description": "The UPC of the product.", 554 656 "example": "0001111041700" 657 + }, 658 + "ratingsAndReviews": { 659 + "type": "object", 660 + "properties": { 661 + "averageOverallRating": { 662 + "type": "number", 663 + "description": "The average overall rating of the product.", 664 + "example": 4.5 665 + }, 666 + "totalReviewCount": { 667 + "type": "integer", 668 + "description": "The total number of reviews for the product.", 669 + "example": 12 670 + } 671 + } 672 + }, 673 + "nutritionInformation": { 674 + "type": "object", 675 + "$ref": "#/components/schemas/products.nutritionInformationModel" 676 + } 677 + } 678 + }, 679 + "products.restrictionsModel": { 680 + "type": "object", 681 + "properties": { 682 + "maximumOrderQuantity": { 683 + "type": "integer", 684 + "example": 10 685 + }, 686 + "minimumOrderQuantity": { 687 + "type": "integer", 688 + "example": 1 689 + }, 690 + "postalCode": { 691 + "type": "array", 692 + "items": { 693 + "type": "string" 694 + }, 695 + "example": [ 696 + "12345", 697 + "67890" 698 + ] 699 + }, 700 + "shippable": { 701 + "type": "boolean", 702 + "example": false 703 + }, 704 + "stateCodes": { 705 + "type": "array", 706 + "items": { 707 + "type": "string" 708 + }, 709 + "example": [ 710 + "CA", 711 + "NY" 712 + ] 713 + } 714 + } 715 + }, 716 + "products.nutritionInformationModel": { 717 + "type": "object", 718 + "properties": { 719 + "ingredientStatement": { 720 + "type": "string", 721 + "example": "\"Milk, skim milk, vitamin A palmitate, and vitamin D3.CONTAINS: MILK." 722 + }, 723 + "dailyValueIntakeReference": { 724 + "type": "string", 725 + "example": "Percent Daily Values are based on a 2,000 calorie diet." 726 + }, 727 + "servingSize": { 728 + "$ref": "#/components/schemas/products.servingSizeModel" 729 + }, 730 + "nutrients": { 731 + "type": "array", 732 + "items": { 733 + "$ref": "#/components/schemas/products.nutrientModel" 734 + } 735 + }, 736 + "preparationState": { 737 + "type": "object", 738 + "properties": { 739 + "code": { 740 + "type": "string", 741 + "example": "UNPREPARED" 742 + }, 743 + "name": { 744 + "type": "string", 745 + "example": "Unprepared" 746 + } 747 + } 748 + }, 749 + "servingsPerPackage": { 750 + "type": "object", 751 + "properties": { 752 + "description": { 753 + "type": "string", 754 + "example": "8.0 Exact" 755 + }, 756 + "value": { 757 + "type": "number", 758 + "example": 8 759 + } 760 + } 761 + }, 762 + "nutritionalRating": { 763 + "type": "string", 764 + "example": "73" 765 + } 766 + } 767 + }, 768 + "products.allergenModel": { 769 + "type": "object", 770 + "properties": { 771 + "levelOfContainmentName": { 772 + "type": "string", 773 + "example": "Free from" 774 + }, 775 + "name": { 776 + "type": "string", 777 + "example": "Tree Nuts and Their Derivatives" 778 + } 779 + } 780 + }, 781 + "products.servingSizeModel": { 782 + "type": "object", 783 + "properties": { 784 + "description": { 785 + "type": "string", 786 + "example": "8 fl oz (240mL)" 787 + }, 788 + "quantity": { 789 + "type": "number", 790 + "example": 240 791 + }, 792 + "unitOfMeasure": { 793 + "type": "object", 794 + "properties": { 795 + "abbreviation": { 796 + "type": "string", 797 + "example": "ml" 798 + }, 799 + "code": { 800 + "type": "string", 801 + "example": "MLT" 802 + }, 803 + "name": { 804 + "type": "string", 805 + "example": "Millilitre" 806 + } 807 + } 808 + } 809 + } 810 + }, 811 + "products.nutrientModel": { 812 + "type": "object", 813 + "properties": { 814 + "code": { 815 + "type": "string", 816 + "example": "CA" 817 + }, 818 + "description": { 819 + "type": "string", 820 + "example": "calcium" 821 + }, 822 + "displayName": { 823 + "type": "string", 824 + "example": "Calcium" 825 + }, 826 + "percentDailyIntake": { 827 + "type": "number", 828 + "example": 25 829 + }, 830 + "quantity": { 831 + "type": "number", 832 + "example": 290 833 + }, 834 + "precision": { 835 + "type": "object", 836 + "properties": { 837 + "code": { 838 + "type": "string", 839 + "example": "APPROXIMATELY" 840 + }, 841 + "name": { 842 + "type": "string", 843 + "example": "Approximately" 844 + } 845 + } 846 + }, 847 + "unitOfMeasure": { 848 + "type": "object", 849 + "properties": { 850 + "abbreviation": { 851 + "type": "string", 852 + "example": "mg" 853 + }, 854 + "code": { 855 + "type": "string", 856 + "example": "MGM" 857 + }, 858 + "name": { 859 + "type": "string", 860 + "example": "Milligram" 861 + } 862 + } 555 863 } 556 864 } 557 865 }, ··· 634 942 "depth": { 635 943 "type": "string", 636 944 "description": "The depth of the product.", 637 - "example": "3.5" 945 + "example": "6" 638 946 }, 639 947 "height": { 640 948 "type": "string", 641 949 "description": "The height of the product.", 642 - "example": "2.0" 950 + "example": "10" 643 951 }, 644 952 "width": { 645 953 "type": "string", 646 954 "description": "The length of the product.", 647 - "example": "4.75" 955 + "example": "6" 956 + }, 957 + "grossWeight": { 958 + "type": "string", 959 + "description": "The gross weight of the product.", 960 + "example": "8.82 [lb_av]" 961 + }, 962 + "netWeight": { 963 + "type": "string", 964 + "description": "The net weight of the product.", 965 + "example": "8.62 [lb_av]" 966 + }, 967 + "averageWeightPerUnit": { 968 + "type": "string", 969 + "description": "The average weight per unit of the product.", 970 + "example": "8.62 [lb_av]" 648 971 } 649 972 } 650 973 }, ··· 741 1064 "type": "number", 742 1065 "description": "The estimated sale price of 1 unit of the item.", 743 1066 "example": 1.59 1067 + }, 1068 + "expirationDate": { 1069 + "$ref": "#/components/schemas/products.dateValueModel" 1070 + }, 1071 + "effectiveDate": { 1072 + "$ref": "#/components/schemas/products.dateValueModel" 1073 + } 1074 + } 1075 + }, 1076 + "products.dateValueModel": { 1077 + "type": "object", 1078 + "properties": { 1079 + "value": { 1080 + "type": "string", 1081 + "format": "date", 1082 + "example": "9999-12-31T00:00:00Z" 1083 + }, 1084 + "timezone": { 1085 + "type": "string", 1086 + "example": "UTC" 744 1087 } 745 1088 } 746 1089 }, ··· 837 1180 ] 838 1181 } 839 1182 ] 840 - } 1183 + }
+9
internal/kroger/products/cfg.yaml
··· 1 + # yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json 2 + package: products 3 + output: client.gen.go 4 + generate: 5 + models: true 6 + client: true 7 + output-options: 8 + overlay: 9 + path: overlay.yaml
+971
internal/kroger/products/client.gen.go
··· 1 + // Package products provides primitives to interact with the openapi HTTP API. 2 + // 3 + // Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT. 4 + package products 5 + 6 + import ( 7 + "context" 8 + "encoding/json" 9 + "fmt" 10 + "io" 11 + "net/http" 12 + "net/url" 13 + "strings" 14 + 15 + "github.com/oapi-codegen/runtime" 16 + ) 17 + 18 + const ( 19 + ClientContextScopes = "ClientContext.Scopes" 20 + ) 21 + 22 + // Defines values for ProductsProductItemInventoryModelStockLevel. 23 + const ( 24 + HIGH ProductsProductItemInventoryModelStockLevel = "HIGH" 25 + LOW ProductsProductItemInventoryModelStockLevel = "LOW" 26 + TEMPORARILYOUTOFSTOCK ProductsProductItemInventoryModelStockLevel = "TEMPORARILY_OUT_OF_STOCK" 27 + ) 28 + 29 + // Valid indicates whether the value is a known member of the ProductsProductItemInventoryModelStockLevel enum. 30 + func (e ProductsProductItemInventoryModelStockLevel) Valid() bool { 31 + switch e { 32 + case HIGH: 33 + return true 34 + case LOW: 35 + return true 36 + case TEMPORARILYOUTOFSTOCK: 37 + return true 38 + default: 39 + return false 40 + } 41 + } 42 + 43 + // Defines values for ProductGetParamsFilterFulfillment. 44 + const ( 45 + Ais ProductGetParamsFilterFulfillment = "ais" 46 + Csp ProductGetParamsFilterFulfillment = "csp" 47 + Dth ProductGetParamsFilterFulfillment = "dth" 48 + Sth ProductGetParamsFilterFulfillment = "sth" 49 + ) 50 + 51 + // Valid indicates whether the value is a known member of the ProductGetParamsFilterFulfillment enum. 52 + func (e ProductGetParamsFilterFulfillment) Valid() bool { 53 + switch e { 54 + case Ais: 55 + return true 56 + case Csp: 57 + return true 58 + case Dth: 59 + return true 60 + case Sth: 61 + return true 62 + default: 63 + return false 64 + } 65 + } 66 + 67 + // APIError defines model for APIError. 68 + type APIError struct { 69 + Code *string `json:"code,omitempty"` 70 + Reason *string `json:"reason,omitempty"` 71 + Timestamp *float32 `json:"timestamp,omitempty"` 72 + } 73 + 74 + // APIErrorForbidden defines model for APIError.forbidden. 75 + type APIErrorForbidden struct { 76 + Errors *struct { 77 + Code *string `json:"code,omitempty"` 78 + Reason *string `json:"reason,omitempty"` 79 + Timestamp *float32 `json:"timestamp,omitempty"` 80 + } `json:"errors,omitempty"` 81 + } 82 + 83 + // APIErrorProductsServerError defines model for APIError.products.serverError. 84 + type APIErrorProductsServerError struct { 85 + Errors *struct { 86 + Code *string `json:"code,omitempty"` 87 + Reason *string `json:"reason,omitempty"` 88 + Timestamp *float32 `json:"timestamp,omitempty"` 89 + } `json:"errors,omitempty"` 90 + } 91 + 92 + // APIErrorUnauthorized defines model for APIError.unauthorized. 93 + type APIErrorUnauthorized struct { 94 + Errors *struct { 95 + Error *string `json:"error,omitempty"` 96 + ErrorDescription *string `json:"error_description,omitempty"` 97 + } `json:"errors,omitempty"` 98 + } 99 + 100 + // InvalidUPC defines model for Invalid_UPC. 101 + type InvalidUPC struct { 102 + Code *string `json:"code,omitempty"` 103 + Reason *string `json:"reason,omitempty"` 104 + Timestamp *float32 `json:"timestamp,omitempty"` 105 + } 106 + 107 + // InvalidLimit defines model for Invalid_limit. 108 + type InvalidLimit struct { 109 + Code *string `json:"code,omitempty"` 110 + Reason *string `json:"reason,omitempty"` 111 + Timestamp *float32 `json:"timestamp,omitempty"` 112 + } 113 + 114 + // InvalidLocationId defines model for Invalid_locationId. 115 + type InvalidLocationId struct { 116 + Code *string `json:"code,omitempty"` 117 + Reason *string `json:"reason,omitempty"` 118 + Timestamp *float32 `json:"timestamp,omitempty"` 119 + } 120 + 121 + // InvalidParameter defines model for Invalid_parameter. 122 + type InvalidParameter struct { 123 + Code *string `json:"code,omitempty"` 124 + Reason *string `json:"reason,omitempty"` 125 + Timestamp *float32 `json:"timestamp,omitempty"` 126 + } 127 + 128 + // UPC The UPC of the product to return. 129 + type UPC = string 130 + 131 + // ProductId The productId of the product to return. 132 + type ProductId = string 133 + 134 + // ProductsAllergenModel defines model for products.allergenModel. 135 + type ProductsAllergenModel struct { 136 + LevelOfContainmentName *string `json:"levelOfContainmentName,omitempty"` 137 + Name *string `json:"name,omitempty"` 138 + } 139 + 140 + // ProductsProductAisleLocationModel defines model for products.productAisleLocationModel. 141 + type ProductsProductAisleLocationModel struct { 142 + // BayNumber The bay number of the aisle. 143 + BayNumber *string `json:"bayNumber,omitempty"` 144 + 145 + // Description The location in the store. 146 + Description *string `json:"description,omitempty"` 147 + 148 + // Number The aisle number in the store. 149 + Number *string `json:"number,omitempty"` 150 + 151 + // NumberOfFacings The number of facings. 152 + NumberOfFacings *string `json:"numberOfFacings,omitempty"` 153 + 154 + // SequenceNumber The sequence of the aisle in the store. 155 + SequenceNumber *string `json:"sequenceNumber,omitempty"` 156 + 157 + // ShelfNumber The shelf number in the aisle. 158 + ShelfNumber *string `json:"shelfNumber,omitempty"` 159 + 160 + // ShelfPositionInBay The position of the shelf in the bay. 161 + ShelfPositionInBay *string `json:"shelfPositionInBay,omitempty"` 162 + 163 + // Side The side of the aisle where the product is located. 164 + Side *string `json:"side,omitempty"` 165 + } 166 + 167 + // ProductsProductBoxedDimensionsModel Information about the product's size. 168 + type ProductsProductBoxedDimensionsModel struct { 169 + // AverageWeightPerUnit The average weight per unit of the product. 170 + AverageWeightPerUnit *string `json:"averageWeightPerUnit,omitempty"` 171 + 172 + // Depth The depth of the product. 173 + Depth *string `json:"depth,omitempty"` 174 + 175 + // GrossWeight The gross weight of the product. 176 + GrossWeight *string `json:"grossWeight,omitempty"` 177 + 178 + // Height The height of the product. 179 + Height *string `json:"height,omitempty"` 180 + 181 + // NetWeight The net weight of the product. 182 + NetWeight *string `json:"netWeight,omitempty"` 183 + 184 + // Width The length of the product. 185 + Width *string `json:"width,omitempty"` 186 + } 187 + 188 + // ProductsProductImageModel Information about the product's image. 189 + type ProductsProductImageModel struct { 190 + Default *bool `json:"default,omitempty"` 191 + 192 + // Id An optional identifier of the image size. 193 + Id *string `json:"id,omitempty"` 194 + 195 + // Perspective A description of the product image view. 196 + Perspective *string `json:"perspective,omitempty"` 197 + 198 + // Sizes An array of image sizes. 199 + Sizes *[]ProductsProductImageSizeModel `json:"sizes,omitempty"` 200 + } 201 + 202 + // ProductsProductImageSizeModel Information about the product's image. 203 + type ProductsProductImageSizeModel struct { 204 + // Id An optional identifier of the image size. 205 + Id *string `json:"id,omitempty"` 206 + 207 + // Size A description of the image size. 208 + Size *string `json:"size,omitempty"` 209 + 210 + // Url The URL location of the image. 211 + Url *string `json:"url,omitempty"` 212 + } 213 + 214 + // ProductsProductItemFulfillmentModel defines model for products.productItemFulfillmentModel. 215 + type ProductsProductItemFulfillmentModel struct { 216 + // Curbside Indicates if the product is available for curbside pickup. 217 + Curbside *bool `json:"curbside,omitempty"` 218 + 219 + // Delivery Indicates if the product is available for home delivery. 220 + Delivery *bool `json:"delivery,omitempty"` 221 + 222 + // Instore Indicates if the product is available in store. This does not indicate that the item is in stock. 223 + Instore *bool `json:"instore,omitempty"` 224 + 225 + // Shiptohome Indicates if the product is available to be shipped from a fulfillment center. 226 + Shiptohome *bool `json:"shiptohome,omitempty"` 227 + } 228 + 229 + // ProductsProductItemInventoryModel defines model for products.productItemInventoryModel. 230 + type ProductsProductItemInventoryModel struct { 231 + // StockLevel Indicates the level of stock. 232 + StockLevel *ProductsProductItemInventoryModelStockLevel `json:"stockLevel,omitempty"` 233 + } 234 + 235 + // ProductsProductItemInventoryModelStockLevel Indicates the level of stock. 236 + type ProductsProductItemInventoryModelStockLevel string 237 + 238 + // ProductsProductItemModel defines model for products.productItemModel. 239 + type ProductsProductItemModel struct { 240 + Favorite *bool `json:"favorite,omitempty"` 241 + Fulfillment *ProductsProductItemFulfillmentModel `json:"fulfillment,omitempty"` 242 + Inventory *ProductsProductItemInventoryModel `json:"inventory,omitempty"` 243 + 244 + // ItemId The UPC of the item. 245 + ItemId *string `json:"itemId,omitempty"` 246 + NationalPrice *ProductsProductItemPriceModel `json:"nationalPrice,omitempty"` 247 + Price *ProductsProductItemPriceModel `json:"price,omitempty"` 248 + 249 + // Size A description of the item size. 250 + Size *string `json:"size,omitempty"` 251 + 252 + // SoldBy Indicates how this item is sold. Values returned are typically either "weight" or "unit" 253 + SoldBy *string `json:"soldBy,omitempty"` 254 + } 255 + 256 + // ProductsProductItemPriceModel defines model for products.productItemPriceModel. 257 + type ProductsProductItemPriceModel struct { 258 + // Promo The sale price of the item. 259 + Promo *float32 `json:"promo,omitempty"` 260 + 261 + // PromoPerUnitEstimate The estimated sale price of 1 unit of the item. 262 + PromoPerUnitEstimate *float32 `json:"promoPerUnitEstimate,omitempty"` 263 + 264 + // Regular The regular price of the item. 265 + Regular *float32 `json:"regular,omitempty"` 266 + 267 + // RegularPerUnitEstimate The estimated price of 1 unit of the item. 268 + RegularPerUnitEstimate *float32 `json:"regularPerUnitEstimate,omitempty"` 269 + } 270 + 271 + // ProductsProductModel defines model for products.productModel. 272 + type ProductsProductModel struct { 273 + // AgeRestriction Indicates if the product has an age restriction. 274 + AgeRestriction *bool `json:"ageRestriction,omitempty"` 275 + AisleLocations *[]ProductsProductAisleLocationModel `json:"aisleLocations,omitempty"` 276 + 277 + // Alcohol Indicates if the product contains alcohol. 278 + Alcohol *bool `json:"alcohol,omitempty"` 279 + 280 + // AlcoholProof The proof of the alcohol in the product. 281 + AlcoholProof *int `json:"alcoholProof,omitempty"` 282 + 283 + // AliasProductIds The alias ProductId of the product to return. 284 + AliasProductIds *[]string `json:"aliasProductIds,omitempty"` 285 + Allergens *[]ProductsAllergenModel `json:"allergens,omitempty"` 286 + 287 + // AllergensDescription The description of the allergens for the product. 288 + AllergensDescription *string `json:"allergensDescription,omitempty"` 289 + 290 + // Brand The brand name of the product. 291 + Brand *string `json:"brand,omitempty"` 292 + 293 + // Categories The category the product belongs to. 294 + Categories *[]string `json:"categories,omitempty"` 295 + 296 + // CertifiedForPassover Indicates if the product is certified for Passover. 297 + CertifiedForPassover *bool `json:"certifiedForPassover,omitempty"` 298 + 299 + // CountryOrigin The country of origin of the product. 300 + CountryOrigin *string `json:"countryOrigin,omitempty"` 301 + 302 + // Description The name of the product. 303 + Description *string `json:"description,omitempty"` 304 + 305 + // Hypoallergenic Indicates if the product is hypoallergenic. 306 + Hypoallergenic *bool `json:"hypoallergenic,omitempty"` 307 + Images *[]ProductsProductImageModel `json:"images,omitempty"` 308 + 309 + // ItemInformation Information about the product's size. 310 + ItemInformation *ProductsProductBoxedDimensionsModel `json:"itemInformation,omitempty"` 311 + Items *[]ProductsProductItemModel `json:"items,omitempty"` 312 + 313 + // ManufacturerDeclarations Any manufacturer declarations for the product. 314 + ManufacturerDeclarations *[]string `json:"manufacturerDeclarations,omitempty"` 315 + 316 + // NonGmo Indicates if the product is non-GMO. 317 + NonGmo *bool `json:"nonGmo,omitempty"` 318 + 319 + // NonGmoClaimName The name of the non-GMO claim. 320 + NonGmoClaimName *string `json:"nonGmoClaimName,omitempty"` 321 + 322 + // OrganicClaimName The name of the organic claim. 323 + OrganicClaimName *string `json:"organicClaimName,omitempty"` 324 + 325 + // ProductId The UPC of the product. 326 + ProductId *string `json:"productId,omitempty"` 327 + 328 + // ProductPageURI The URI of the product page. 329 + ProductPageURI *string `json:"productPageURI,omitempty"` 330 + RatingsAndReviews *struct { 331 + // AverageOverallRating The average overall rating of the product. 332 + AverageOverallRating *float32 `json:"averageOverallRating,omitempty"` 333 + 334 + // TotalReviewCount The total number of reviews for the product. 335 + TotalReviewCount *int `json:"totalReviewCount,omitempty"` 336 + } `json:"ratingsAndReviews,omitempty"` 337 + 338 + // ReceiptDescription The description of the product to appear on the receipt. 339 + ReceiptDescription *string `json:"receiptDescription,omitempty"` 340 + Retstrictions *ProductsRestrictionsModel `json:"retstrictions,omitempty"` 341 + 342 + // SnapEligible Indicates if the product is eligible for SNAP benefits. 343 + SnapEligible *bool `json:"snapEligible,omitempty"` 344 + 345 + // Temperature Information about the item's temperature requirements. 346 + Temperature *ProductsProductTemperatureModel `json:"temperature,omitempty"` 347 + 348 + // Upc The UPC of the product. 349 + Upc *string `json:"upc,omitempty"` 350 + 351 + // Warnings Any warnings for the product. 352 + Warnings *string `json:"warnings,omitempty"` 353 + } 354 + 355 + // ProductsProductPayloadModel defines model for products.productPayloadModel. 356 + type ProductsProductPayloadModel struct { 357 + Data *ProductsProductModel `json:"data,omitempty"` 358 + Meta *map[string]interface{} `json:"meta,omitempty"` 359 + } 360 + 361 + // ProductsProductTemperatureModel Information about the item's temperature requirements. 362 + type ProductsProductTemperatureModel struct { 363 + // HeatSensitive Indicates if the item is heat sensitive. 364 + HeatSensitive *bool `json:"heatSensitive,omitempty"` 365 + 366 + // Indicator Information about the product's storage temperature. 367 + Indicator *string `json:"indicator,omitempty"` 368 + } 369 + 370 + // ProductsProductsPayloadModel defines model for products.productsPayloadModel. 371 + type ProductsProductsPayloadModel struct { 372 + Data *[]ProductsProductModel `json:"data,omitempty"` 373 + Meta *map[string]interface{} `json:"meta,omitempty"` 374 + } 375 + 376 + // ProductsRestrictionsModel defines model for products.restrictionsModel. 377 + type ProductsRestrictionsModel struct { 378 + MaximumOrderQuantity *int `json:"maximumOrderQuantity,omitempty"` 379 + MinimumOrderQuantity *int `json:"minimumOrderQuantity,omitempty"` 380 + PostalCode *[]string `json:"postalCode,omitempty"` 381 + Shippable *bool `json:"shippable,omitempty"` 382 + StateCodes *[]string `json:"stateCodes,omitempty"` 383 + } 384 + 385 + // ProductGetParams defines parameters for ProductGet. 386 + type ProductGetParams struct { 387 + // FilterTerm A search term to filter product results. As an example, you could input _milk_, _bread_, or _salt_. <br><br><b>Note</b> - Search terms are limited to a maximum of 8 words. Each new space in the search term denotes a new word. 388 + FilterTerm *string `form:"filter.term,omitempty" json:"filter.term,omitempty"` 389 + 390 + // FilterLocationId The locationId of the location. When using this filter, only products available at that location are returned. 391 + FilterLocationId *string `form:"filter.locationId,omitempty" json:"filter.locationId,omitempty"` 392 + 393 + // FilterProductId The productId of the products(s) to return. For more than one item, the list must be comma-separated. When used, all other query parameters are ignored. 394 + FilterProductId *string `form:"filter.productId,omitempty" json:"filter.productId,omitempty"` 395 + 396 + // FilterBrand The brand name of the products to return. When using this filter, only products by that brand are returned. Brand names are case-sensitive, and lists must be pipe-separated. 397 + FilterBrand *string `form:"filter.brand,omitempty" json:"filter.brand,omitempty"` 398 + 399 + // FilterFulfillment 'The available fulfillment types of the product(s) to return. 400 + // Fulfillment types are case-sensitive, and lists must be comma-separated. 401 + // Must be one or more of the follow types: <ul> <li> `ais` - Available In 402 + // Store</li> <li> `csp` - Curbside Pickup</li> <li> `dth` - Delivery To Home</li> 403 + // <li> `sth` - Ship To Home</li> </ui>' 404 + FilterFulfillment *ProductGetParamsFilterFulfillment `form:"filter.fulfillment,omitempty" json:"filter.fulfillment,omitempty"` 405 + 406 + // FilterStart The number of products to skip. 407 + FilterStart *int `form:"filter.start,omitempty" json:"filter.start,omitempty"` 408 + 409 + // FilterLimit The number of products to return. 410 + FilterLimit *int `form:"filter.limit,omitempty" json:"filter.limit,omitempty"` 411 + } 412 + 413 + // ProductGetParamsFilterFulfillment defines parameters for ProductGet. 414 + type ProductGetParamsFilterFulfillment string 415 + 416 + // ProductGetIDParams defines parameters for ProductGetID. 417 + type ProductGetIDParams struct { 418 + // FilterLocationId The locationId of the location. When using this filter, only products available at that location are returned. 419 + FilterLocationId *string `form:"filter.locationId,omitempty" json:"filter.locationId,omitempty"` 420 + } 421 + 422 + // RequestEditorFn is the function signature for the RequestEditor callback function 423 + type RequestEditorFn func(ctx context.Context, req *http.Request) error 424 + 425 + // Doer performs HTTP requests. 426 + // 427 + // The standard http.Client implements this interface. 428 + type HttpRequestDoer interface { 429 + Do(req *http.Request) (*http.Response, error) 430 + } 431 + 432 + // Client which conforms to the OpenAPI3 specification for this service. 433 + type Client struct { 434 + // The endpoint of the server conforming to this interface, with scheme, 435 + // https://api.deepmap.com for example. This can contain a path relative 436 + // to the server, such as https://api.deepmap.com/dev-test, and all the 437 + // paths in the swagger spec will be appended to the server. 438 + Server string 439 + 440 + // Doer for performing requests, typically a *http.Client with any 441 + // customized settings, such as certificate chains. 442 + Client HttpRequestDoer 443 + 444 + // A list of callbacks for modifying requests which are generated before sending over 445 + // the network. 446 + RequestEditors []RequestEditorFn 447 + } 448 + 449 + // ClientOption allows setting custom parameters during construction 450 + type ClientOption func(*Client) error 451 + 452 + // Creates a new Client, with reasonable defaults 453 + func NewClient(server string, opts ...ClientOption) (*Client, error) { 454 + // create a client with sane default values 455 + client := Client{ 456 + Server: server, 457 + } 458 + // mutate client and add all optional params 459 + for _, o := range opts { 460 + if err := o(&client); err != nil { 461 + return nil, err 462 + } 463 + } 464 + // ensure the server URL always has a trailing slash 465 + if !strings.HasSuffix(client.Server, "/") { 466 + client.Server += "/" 467 + } 468 + // create httpClient, if not already present 469 + if client.Client == nil { 470 + client.Client = &http.Client{} 471 + } 472 + return &client, nil 473 + } 474 + 475 + // WithHTTPClient allows overriding the default Doer, which is 476 + // automatically created using http.Client. This is useful for tests. 477 + func WithHTTPClient(doer HttpRequestDoer) ClientOption { 478 + return func(c *Client) error { 479 + c.Client = doer 480 + return nil 481 + } 482 + } 483 + 484 + // WithRequestEditorFn allows setting up a callback function, which will be 485 + // called right before sending the request. This can be used to mutate the request. 486 + func WithRequestEditorFn(fn RequestEditorFn) ClientOption { 487 + return func(c *Client) error { 488 + c.RequestEditors = append(c.RequestEditors, fn) 489 + return nil 490 + } 491 + } 492 + 493 + // The interface specification for the client above. 494 + type ClientInterface interface { 495 + // ProductGet request 496 + ProductGet(ctx context.Context, params *ProductGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) 497 + 498 + // ProductGetID request 499 + ProductGetID(ctx context.Context, id struct { 500 + union json.RawMessage 501 + }, params *ProductGetIDParams, reqEditors ...RequestEditorFn) (*http.Response, error) 502 + } 503 + 504 + func (c *Client) ProductGet(ctx context.Context, params *ProductGetParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 505 + req, err := NewProductGetRequest(c.Server, params) 506 + if err != nil { 507 + return nil, err 508 + } 509 + req = req.WithContext(ctx) 510 + if err := c.applyEditors(ctx, req, reqEditors); err != nil { 511 + return nil, err 512 + } 513 + return c.Client.Do(req) 514 + } 515 + 516 + func (c *Client) ProductGetID(ctx context.Context, id struct { 517 + union json.RawMessage 518 + }, params *ProductGetIDParams, reqEditors ...RequestEditorFn) (*http.Response, error) { 519 + req, err := NewProductGetIDRequest(c.Server, id, params) 520 + if err != nil { 521 + return nil, err 522 + } 523 + req = req.WithContext(ctx) 524 + if err := c.applyEditors(ctx, req, reqEditors); err != nil { 525 + return nil, err 526 + } 527 + return c.Client.Do(req) 528 + } 529 + 530 + // NewProductGetRequest generates requests for ProductGet 531 + func NewProductGetRequest(server string, params *ProductGetParams) (*http.Request, error) { 532 + var err error 533 + 534 + serverURL, err := url.Parse(server) 535 + if err != nil { 536 + return nil, err 537 + } 538 + 539 + operationPath := fmt.Sprintf("/v1/products") 540 + if operationPath[0] == '/' { 541 + operationPath = "." + operationPath 542 + } 543 + 544 + queryURL, err := serverURL.Parse(operationPath) 545 + if err != nil { 546 + return nil, err 547 + } 548 + 549 + if params != nil { 550 + queryValues := queryURL.Query() 551 + 552 + if params.FilterTerm != nil { 553 + 554 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.term", *params.FilterTerm, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 555 + return nil, err 556 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 557 + return nil, err 558 + } else { 559 + for k, v := range parsed { 560 + for _, v2 := range v { 561 + queryValues.Add(k, v2) 562 + } 563 + } 564 + } 565 + 566 + } 567 + 568 + if params.FilterLocationId != nil { 569 + 570 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.locationId", *params.FilterLocationId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 571 + return nil, err 572 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 573 + return nil, err 574 + } else { 575 + for k, v := range parsed { 576 + for _, v2 := range v { 577 + queryValues.Add(k, v2) 578 + } 579 + } 580 + } 581 + 582 + } 583 + 584 + if params.FilterProductId != nil { 585 + 586 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.productId", *params.FilterProductId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 587 + return nil, err 588 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 589 + return nil, err 590 + } else { 591 + for k, v := range parsed { 592 + for _, v2 := range v { 593 + queryValues.Add(k, v2) 594 + } 595 + } 596 + } 597 + 598 + } 599 + 600 + if params.FilterBrand != nil { 601 + 602 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.brand", *params.FilterBrand, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 603 + return nil, err 604 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 605 + return nil, err 606 + } else { 607 + for k, v := range parsed { 608 + for _, v2 := range v { 609 + queryValues.Add(k, v2) 610 + } 611 + } 612 + } 613 + 614 + } 615 + 616 + if params.FilterFulfillment != nil { 617 + 618 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.fulfillment", *params.FilterFulfillment, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 619 + return nil, err 620 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 621 + return nil, err 622 + } else { 623 + for k, v := range parsed { 624 + for _, v2 := range v { 625 + queryValues.Add(k, v2) 626 + } 627 + } 628 + } 629 + 630 + } 631 + 632 + if params.FilterStart != nil { 633 + 634 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.start", *params.FilterStart, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { 635 + return nil, err 636 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 637 + return nil, err 638 + } else { 639 + for k, v := range parsed { 640 + for _, v2 := range v { 641 + queryValues.Add(k, v2) 642 + } 643 + } 644 + } 645 + 646 + } 647 + 648 + if params.FilterLimit != nil { 649 + 650 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.limit", *params.FilterLimit, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { 651 + return nil, err 652 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 653 + return nil, err 654 + } else { 655 + for k, v := range parsed { 656 + for _, v2 := range v { 657 + queryValues.Add(k, v2) 658 + } 659 + } 660 + } 661 + 662 + } 663 + 664 + queryURL.RawQuery = queryValues.Encode() 665 + } 666 + 667 + req, err := http.NewRequest("GET", queryURL.String(), nil) 668 + if err != nil { 669 + return nil, err 670 + } 671 + 672 + return req, nil 673 + } 674 + 675 + // NewProductGetIDRequest generates requests for ProductGetID 676 + func NewProductGetIDRequest(server string, id struct { 677 + union json.RawMessage 678 + }, params *ProductGetIDParams) (*http.Request, error) { 679 + var err error 680 + 681 + var pathParam0 string 682 + 683 + pathParam0, err = runtime.StyleParamWithOptions("simple", false, "id", id, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "", Format: ""}) 684 + if err != nil { 685 + return nil, err 686 + } 687 + 688 + serverURL, err := url.Parse(server) 689 + if err != nil { 690 + return nil, err 691 + } 692 + 693 + operationPath := fmt.Sprintf("/v1/products/%s", pathParam0) 694 + if operationPath[0] == '/' { 695 + operationPath = "." + operationPath 696 + } 697 + 698 + queryURL, err := serverURL.Parse(operationPath) 699 + if err != nil { 700 + return nil, err 701 + } 702 + 703 + if params != nil { 704 + queryValues := queryURL.Query() 705 + 706 + if params.FilterLocationId != nil { 707 + 708 + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filter.locationId", *params.FilterLocationId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { 709 + return nil, err 710 + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 711 + return nil, err 712 + } else { 713 + for k, v := range parsed { 714 + for _, v2 := range v { 715 + queryValues.Add(k, v2) 716 + } 717 + } 718 + } 719 + 720 + } 721 + 722 + queryURL.RawQuery = queryValues.Encode() 723 + } 724 + 725 + req, err := http.NewRequest("GET", queryURL.String(), nil) 726 + if err != nil { 727 + return nil, err 728 + } 729 + 730 + return req, nil 731 + } 732 + 733 + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { 734 + for _, r := range c.RequestEditors { 735 + if err := r(ctx, req); err != nil { 736 + return err 737 + } 738 + } 739 + for _, r := range additionalEditors { 740 + if err := r(ctx, req); err != nil { 741 + return err 742 + } 743 + } 744 + return nil 745 + } 746 + 747 + // ClientWithResponses builds on ClientInterface to offer response payloads 748 + type ClientWithResponses struct { 749 + ClientInterface 750 + } 751 + 752 + // NewClientWithResponses creates a new ClientWithResponses, which wraps 753 + // Client with return type handling 754 + func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { 755 + client, err := NewClient(server, opts...) 756 + if err != nil { 757 + return nil, err 758 + } 759 + return &ClientWithResponses{client}, nil 760 + } 761 + 762 + // WithBaseURL overrides the baseURL. 763 + func WithBaseURL(baseURL string) ClientOption { 764 + return func(c *Client) error { 765 + newBaseURL, err := url.Parse(baseURL) 766 + if err != nil { 767 + return err 768 + } 769 + c.Server = newBaseURL.String() 770 + return nil 771 + } 772 + } 773 + 774 + // ClientWithResponsesInterface is the interface specification for the client with responses above. 775 + type ClientWithResponsesInterface interface { 776 + // ProductGetWithResponse request 777 + ProductGetWithResponse(ctx context.Context, params *ProductGetParams, reqEditors ...RequestEditorFn) (*ProductGetResponse, error) 778 + 779 + // ProductGetIDWithResponse request 780 + ProductGetIDWithResponse(ctx context.Context, id struct { 781 + union json.RawMessage 782 + }, params *ProductGetIDParams, reqEditors ...RequestEditorFn) (*ProductGetIDResponse, error) 783 + } 784 + 785 + type ProductGetResponse struct { 786 + Body []byte 787 + HTTPResponse *http.Response 788 + JSON200 *ProductsProductsPayloadModel 789 + JSON400 *struct { 790 + union json.RawMessage 791 + } 792 + JSON401 *APIErrorUnauthorized 793 + JSON403 *APIErrorForbidden 794 + JSON500 *APIErrorProductsServerError 795 + } 796 + 797 + // Status returns HTTPResponse.Status 798 + func (r ProductGetResponse) Status() string { 799 + if r.HTTPResponse != nil { 800 + return r.HTTPResponse.Status 801 + } 802 + return http.StatusText(0) 803 + } 804 + 805 + // StatusCode returns HTTPResponse.StatusCode 806 + func (r ProductGetResponse) StatusCode() int { 807 + if r.HTTPResponse != nil { 808 + return r.HTTPResponse.StatusCode 809 + } 810 + return 0 811 + } 812 + 813 + type ProductGetIDResponse struct { 814 + Body []byte 815 + HTTPResponse *http.Response 816 + JSON200 *ProductsProductPayloadModel 817 + JSON400 *struct { 818 + union json.RawMessage 819 + } 820 + JSON401 *APIErrorUnauthorized 821 + JSON403 *APIErrorForbidden 822 + JSON500 *APIErrorProductsServerError 823 + } 824 + 825 + // Status returns HTTPResponse.Status 826 + func (r ProductGetIDResponse) Status() string { 827 + if r.HTTPResponse != nil { 828 + return r.HTTPResponse.Status 829 + } 830 + return http.StatusText(0) 831 + } 832 + 833 + // StatusCode returns HTTPResponse.StatusCode 834 + func (r ProductGetIDResponse) StatusCode() int { 835 + if r.HTTPResponse != nil { 836 + return r.HTTPResponse.StatusCode 837 + } 838 + return 0 839 + } 840 + 841 + // ProductGetWithResponse request returning *ProductGetResponse 842 + func (c *ClientWithResponses) ProductGetWithResponse(ctx context.Context, params *ProductGetParams, reqEditors ...RequestEditorFn) (*ProductGetResponse, error) { 843 + rsp, err := c.ProductGet(ctx, params, reqEditors...) 844 + if err != nil { 845 + return nil, err 846 + } 847 + return ParseProductGetResponse(rsp) 848 + } 849 + 850 + // ProductGetIDWithResponse request returning *ProductGetIDResponse 851 + func (c *ClientWithResponses) ProductGetIDWithResponse(ctx context.Context, id struct { 852 + union json.RawMessage 853 + }, params *ProductGetIDParams, reqEditors ...RequestEditorFn) (*ProductGetIDResponse, error) { 854 + rsp, err := c.ProductGetID(ctx, id, params, reqEditors...) 855 + if err != nil { 856 + return nil, err 857 + } 858 + return ParseProductGetIDResponse(rsp) 859 + } 860 + 861 + // ParseProductGetResponse parses an HTTP response from a ProductGetWithResponse call 862 + func ParseProductGetResponse(rsp *http.Response) (*ProductGetResponse, error) { 863 + bodyBytes, err := io.ReadAll(rsp.Body) 864 + defer func() { _ = rsp.Body.Close() }() 865 + if err != nil { 866 + return nil, err 867 + } 868 + 869 + response := &ProductGetResponse{ 870 + Body: bodyBytes, 871 + HTTPResponse: rsp, 872 + } 873 + 874 + switch { 875 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 876 + var dest ProductsProductsPayloadModel 877 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 878 + return nil, err 879 + } 880 + response.JSON200 = &dest 881 + 882 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 883 + var dest struct { 884 + union json.RawMessage 885 + } 886 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 887 + return nil, err 888 + } 889 + response.JSON400 = &dest 890 + 891 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 892 + var dest APIErrorUnauthorized 893 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 894 + return nil, err 895 + } 896 + response.JSON401 = &dest 897 + 898 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: 899 + var dest APIErrorForbidden 900 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 901 + return nil, err 902 + } 903 + response.JSON403 = &dest 904 + 905 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 906 + var dest APIErrorProductsServerError 907 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 908 + return nil, err 909 + } 910 + response.JSON500 = &dest 911 + 912 + } 913 + 914 + return response, nil 915 + } 916 + 917 + // ParseProductGetIDResponse parses an HTTP response from a ProductGetIDWithResponse call 918 + func ParseProductGetIDResponse(rsp *http.Response) (*ProductGetIDResponse, error) { 919 + bodyBytes, err := io.ReadAll(rsp.Body) 920 + defer func() { _ = rsp.Body.Close() }() 921 + if err != nil { 922 + return nil, err 923 + } 924 + 925 + response := &ProductGetIDResponse{ 926 + Body: bodyBytes, 927 + HTTPResponse: rsp, 928 + } 929 + 930 + switch { 931 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: 932 + var dest ProductsProductPayloadModel 933 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 934 + return nil, err 935 + } 936 + response.JSON200 = &dest 937 + 938 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: 939 + var dest struct { 940 + union json.RawMessage 941 + } 942 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 943 + return nil, err 944 + } 945 + response.JSON400 = &dest 946 + 947 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: 948 + var dest APIErrorUnauthorized 949 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 950 + return nil, err 951 + } 952 + response.JSON401 = &dest 953 + 954 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: 955 + var dest APIErrorForbidden 956 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 957 + return nil, err 958 + } 959 + response.JSON403 = &dest 960 + 961 + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: 962 + var dest APIErrorProductsServerError 963 + if err := json.Unmarshal(bodyBytes, &dest); err != nil { 964 + return nil, err 965 + } 966 + response.JSON500 = &dest 967 + 968 + } 969 + 970 + return response, nil 971 + }
+3
internal/kroger/products/generate.go
··· 1 + package products 2 + 3 + //go:generate go tool oapi-codegen -config cfg.yaml -include-operation-ids productGet,productGetID openapi.json
+17
internal/kroger/products/overlay.yaml
··· 1 + overlay: 1.0.0 2 + info: 3 + title: Kroger Products client generation overlay 4 + version: 0.0.0 5 + actions: 6 + - target: $.components.schemas["products.productModel"].properties.nutritionInformation 7 + description: Drop field because Kroger returns both object and array shapes and staples does not use it. 8 + remove: true 9 + - target: $.components.schemas["products.productModel"].properties.sweeteningMethods 10 + description: Drop field because Kroger returns both object and array shapes and staples does not use it. 11 + remove: true 12 + - target: $.components.schemas["products.productItemPriceModel"].properties.expirationDate 13 + description: Drop field because Kroger returns date-time strings while the spec declares date-only values and staples does not use it. 14 + remove: true 15 + - target: $.components.schemas["products.productItemPriceModel"].properties.effectiveDate 16 + description: Drop field because Kroger returns date-time strings while the spec declares date-only values and staples does not use it. 17 + remove: true
+80 -25
internal/kroger/staples.go
··· 7 7 "log/slog" 8 8 "net/http" 9 9 "slices" 10 - "strconv" 10 + "strings" 11 11 12 + "careme/internal/ai" 13 + "careme/internal/config" 14 + "careme/internal/kroger/products" 12 15 "careme/internal/parallelism" 13 16 14 17 "github.com/samber/lo" ··· 47 50 // internal? 48 51 type StaplesProvider struct { 49 52 identityProvider 50 - client ClientWithResponsesInterface 53 + client *products.ClientWithResponses 51 54 } 52 55 53 - func NewStaplesProvider(client ClientWithResponsesInterface) StaplesProvider { 54 - return StaplesProvider{client: client} 56 + func NewStaplesProvider(cfg *config.Config, httpClient *http.Client) (*StaplesProvider, error) { 57 + client, err := NewProductsClientFromConfig(cfg, httpClient) 58 + if err != nil { 59 + return nil, err 60 + } 61 + return &StaplesProvider{client: client}, nil 55 62 } 56 63 57 - func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]Ingredient, error) { 58 - return parallelism.Flatten(defaultStaples(), func(category staplesFilter) ([]Ingredient, error) { 64 + func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 65 + return parallelism.Flatten(defaultStaples(), func(category staplesFilter) ([]ai.InputIngredient, error) { 59 66 ingredients, err := searchIngredients(ctx, p.client, locationID, category.Term, category.Brands, category.Frozen, 0) 60 67 if err != nil { 61 68 slog.WarnContext(ctx, "Failed to fetch category", "category", category.Term, "location", locationID, "error", err) 62 69 return nil, err 63 70 } 64 71 slog.InfoContext(ctx, "Found ingredients for category", "count", len(ingredients), "category", category.Term, "location", locationID) 65 - return ingredients, nil 72 + return lo.Map(ingredients, inputIngredientFromKrogerIngredient), nil 66 73 }) 67 74 } 68 75 69 - func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]Ingredient, error) { 70 - return searchIngredients(ctx, p.client, locationID, searchTerm, []string{"*"}, false, skip) 76 + func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 77 + ingredients, err := searchIngredients(ctx, p.client, locationID, searchTerm, []string{"*"}, false, skip) 78 + if err != nil { 79 + return nil, err 80 + } 81 + return lo.Map(ingredients, inputIngredientFromKrogerIngredient), nil 71 82 } 72 83 73 - func searchIngredients(ctx context.Context, client ClientWithResponsesInterface, locationID, term string, brands []string, frozen bool, skip int) ([]Ingredient, error) { 84 + var availableInStore = products.Ais 85 + 86 + func searchIngredients(ctx context.Context, client *products.ClientWithResponses, locationID, term string, brands []string, frozen bool, skip int) ([]Ingredient, error) { 74 87 limit := 50 75 - limitStr := strconv.Itoa(limit) 76 - startStr := strconv.Itoa(skip) 77 - products, err := client.ProductSearchWithResponse(ctx, &ProductSearchParams{ 78 - FilterLocationId: &locationID, 79 - FilterTerm: &term, 80 - FilterLimit: &limitStr, 81 - FilterStart: &startStr, 88 + 89 + productResults, err := client.ProductGetWithResponse(ctx, &products.ProductGetParams{ 90 + FilterLocationId: &locationID, 91 + FilterTerm: &term, 92 + FilterLimit: &limit, 93 + FilterStart: &skip, 94 + FilterFulfillment: &availableInStore, 82 95 }) 83 96 if err != nil { 84 97 return nil, fmt.Errorf("kroger product search request failed: %w", err) 85 98 } 86 - if err := requireSuccess(products.StatusCode(), productSearchErrorPayload(products)); err != nil { 99 + if err := requireSuccess(productResults.StatusCode(), productSearchErrorPayload(productResults)); err != nil { 87 100 return nil, err 88 101 } 89 102 90 103 var ingredients []Ingredient 91 104 92 - for _, product := range *products.JSON200.Data { 105 + for _, product := range *productResults.JSON200.Data { 93 106 wildcard := len(brands) > 0 && brands[0] == "*" 94 107 95 108 if product.Brand != nil && !slices.Contains(brands, toStr(product.Brand)) && !wildcard { ··· 103 116 continue 104 117 } 105 118 119 + if item.Inventory != nil && item.Inventory.StockLevel != nil && *item.Inventory.StockLevel == products.TEMPORARILYOUTOFSTOCK { 120 + // TODO pass along low stock levels to AI to use in recipe planning 121 + // slog.WarnContext(ctx, "OOS", "description", *product.Description) 122 + continue 123 + } 124 + 106 125 var aisle *string 107 126 if product.AisleLocations != nil && len(*product.AisleLocations) > 0 { 108 127 aisle = (*product.AisleLocations)[0].Number ··· 130 149 //Taxonomy: product., 131 150 // CountryOrigin: product.CountryOrigin, 132 151 // Favorite: item.Favorite, 133 - // InventoryStockLevel: item.InventoryStockLevel), 134 152 } 135 153 } 136 154 137 155 // recursion is pretty dumb pagination 138 156 // kroger limites us to 250 139 - if len(*products.JSON200.Data) == limit && skip < 250 { 157 + if len(*productResults.JSON200.Data) == limit && skip < 250 { 140 158 page, err := searchIngredients(ctx, client, locationID, term, brands, frozen, skip+limit) 141 159 if err == nil { 142 160 ingredients = append(ingredients, page...) ··· 146 164 return ingredients, nil 147 165 } 148 166 167 + func inputIngredientFromKrogerIngredient(ingredient Ingredient, _ int) ai.InputIngredient { 168 + return ai.NormalizeInputIngredient(ai.InputIngredient{ 169 + ProductID: strings.TrimSpace(toStr(ingredient.ProductId)), 170 + AisleNumber: strings.TrimSpace(toStr(ingredient.AisleNumber)), 171 + Brand: strings.TrimSpace(toStr(ingredient.Brand)), 172 + Description: strings.TrimSpace(toStr(ingredient.Description)), 173 + Size: strings.TrimSpace(toStr(ingredient.Size)), 174 + PriceRegular: clonePrice(ingredient.PriceRegular), 175 + PriceSale: clonePrice(ingredient.PriceSale), 176 + Categories: categoriesFromPtr(ingredient.Categories), 177 + }) 178 + } 179 + 180 + func categoriesFromPtr(ptr *[]string) []string { 181 + if ptr == nil { 182 + return nil 183 + } 184 + return append([]string(nil), (*ptr)...) 185 + } 186 + 187 + func clonePrice(price *float32) *float32 { 188 + if price == nil { 189 + return nil 190 + } 191 + value := *price 192 + return &value 193 + } 194 + 149 195 func defaultStaples() []staplesFilter { 150 196 return append(ProduceFilters(), []staplesFilter{ 151 197 { ··· 172 218 Term: "lamb", 173 219 Brands: []string{"Simple Truth"}, 174 220 }, 221 + { 222 + Term: "grains", 223 + Brands: []string{"*"}, 224 + }, 225 + { 226 + Term: "pasta", 227 + Brands: []string{"*"}, // Should we just put our thumb on the scale 228 + }, 229 + // TODO dairy, international 175 230 }...) 176 231 } 177 232 ··· 200 255 return krogerError(statusCode, payload) 201 256 } 202 257 203 - func productSearchErrorPayload(resp *ProductSearchResponse) any { 258 + func productSearchErrorPayload(resp *products.ProductGetResponse) any { 204 259 if resp == nil { 205 260 return nil 261 + } 262 + if len(resp.Body) != 0 { 263 + return json.RawMessage(resp.Body) 206 264 } 207 265 if resp.JSON400 != nil { 208 266 return resp.JSON400 ··· 212 270 } 213 271 if resp.JSON500 != nil { 214 272 return resp.JSON500 215 - } 216 - if len(resp.Body) != 0 { 217 - return json.RawMessage(resp.Body) 218 273 } 219 274 return nil 220 275 }
+93 -29
internal/kroger/staples_test.go
··· 3 3 import ( 4 4 "net/http" 5 5 "net/http/httptest" 6 + "slices" 6 7 "strings" 7 8 "testing" 9 + 10 + "careme/internal/kroger/products" 8 11 ) 9 12 10 13 func TestIdentityProviderSignature_UsesJSONStaples(t *testing.T) { ··· 16 19 } 17 20 } 18 21 19 - func TestParseProductSearchResponse_DecodesStructuredJSON400(t *testing.T) { 22 + func TestParseProductGetResponse_KeepsJSON400Body(t *testing.T) { 20 23 rsp := httptest.NewRecorder() 21 24 rsp.Header().Set("Content-Type", "application/json") 22 25 rsp.WriteHeader(http.StatusBadRequest) 23 26 _, _ = rsp.WriteString(`{"errors":{"timestamp":1776969026460,"code":"PRODUCT-2011","reason":"Field 'locationId' must have a length of 8 alphanumeric characters"}}`) 24 27 25 - parsed, err := ParseProductSearchResponse(rsp.Result()) 28 + parsed, err := products.ParseProductGetResponse(rsp.Result()) 26 29 if err != nil { 27 - t.Fatalf("ParseProductSearchResponse returned error: %v", err) 30 + t.Fatalf("ParseProductGetResponse returned error: %v", err) 28 31 } 29 - if parsed.JSON400 == nil || parsed.JSON400.Errors == nil { 30 - t.Fatalf("expected JSON400 error payload, got %+v", parsed.JSON400) 32 + if parsed.JSON400 == nil { 33 + t.Fatalf("expected JSON400 marker, got %+v", parsed.JSON400) 34 + } 35 + if got := string(parsed.Body); !strings.Contains(got, "PRODUCT-2011") || !strings.Contains(got, "length of 8") { 36 + t.Fatalf("expected raw body to include kroger error details, got %q", got) 31 37 } 32 - if got, want := toStr(parsed.JSON400.Errors.Code), "PRODUCT-2011"; got != want { 33 - t.Fatalf("unexpected error code: got %q want %q", got, want) 38 + } 39 + 40 + func TestParseProductGetResponse_IgnoresUnusedPriceDateTimes(t *testing.T) { 41 + rsp := httptest.NewRecorder() 42 + rsp.Header().Set("Content-Type", "application/json") 43 + rsp.WriteHeader(http.StatusOK) 44 + _, _ = rsp.WriteString(`{ 45 + "data": [{ 46 + "items": [{ 47 + "price": { 48 + "expirationDate": {"value": "9999-12-31T00:00:00Z", "timezone": "UTC"}, 49 + "effectiveDate": {"value": "2026-04-29T03:59:59.999Z", "timezone": "UTC"} 50 + } 51 + }] 52 + }] 53 + }`) 54 + 55 + parsed, err := products.ParseProductGetResponse(rsp.Result()) 56 + if err != nil { 57 + t.Fatalf("ParseProductGetResponse returned error: %v", err) 34 58 } 35 - if got := toStr(parsed.JSON400.Errors.Reason); !strings.Contains(got, "length of 8") { 36 - t.Fatalf("unexpected error reason: %q", got) 59 + if parsed.JSON200 == nil || parsed.JSON200.Data == nil { 60 + t.Fatalf("expected JSON200 payload, got %+v", parsed.JSON200) 37 61 } 38 62 } 39 63 40 - func TestProductSearchErrorPayloadPrefersDecodedJSON400(t *testing.T) { 41 - code := "PRODUCT-2011" 42 - reason := "Field 'locationId' must have a length of 8 alphanumeric characters" 43 - resp := &ProductSearchResponse{ 44 - JSON400: &struct { 45 - Errors *struct { 46 - Code *string `json:"code,omitempty"` 47 - Reason *string `json:"reason,omitempty"` 48 - Timestamp *float32 `json:"timestamp,omitempty"` 49 - } `json:"errors,omitempty"` 50 - }{ 51 - Errors: &struct { 52 - Code *string `json:"code,omitempty"` 53 - Reason *string `json:"reason,omitempty"` 54 - Timestamp *float32 `json:"timestamp,omitempty"` 55 - }{ 56 - Code: &code, 57 - Reason: &reason, 58 - }, 59 - }, 64 + func TestParseProductGetResponse_IgnoresUnusedNutritionInformationArray(t *testing.T) { 65 + rsp := httptest.NewRecorder() 66 + rsp.Header().Set("Content-Type", "application/json") 67 + rsp.WriteHeader(http.StatusOK) 68 + _, _ = rsp.WriteString(`{ 69 + "data": [{ 70 + "nutritionInformation": [{ 71 + "ingredientStatement": "beef" 72 + }] 73 + }] 74 + }`) 75 + 76 + parsed, err := products.ParseProductGetResponse(rsp.Result()) 77 + if err != nil { 78 + t.Fatalf("ParseProductGetResponse returned error: %v", err) 79 + } 80 + if parsed.JSON200 == nil || parsed.JSON200.Data == nil { 81 + t.Fatalf("expected JSON200 payload, got %+v", parsed.JSON200) 82 + } 83 + } 84 + 85 + func TestProductSearchErrorPayloadPrefersRawBody(t *testing.T) { 86 + resp := &products.ProductGetResponse{ 87 + Body: []byte(`{"errors":{"code":"PRODUCT-2011","reason":"Field 'locationId' must have a length of 8 alphanumeric characters"}}`), 60 88 } 61 89 62 90 payload := productSearchErrorPayload(resp) ··· 67 95 t.Fatalf("expected krogerError to include decoded payload, got %v", krogerError(http.StatusBadRequest, payload)) 68 96 } 69 97 } 98 + 99 + func TestInputIngredientFromKrogerIngredientMapsFields(t *testing.T) { 100 + regular := float32(4.99) 101 + sale := float32(3.49) 102 + categories := []string{"Produce", "Fresh Fruit"} 103 + ingredient := inputIngredientFromKrogerIngredient(Ingredient{ 104 + ProductId: stringPtr(" apple-1 "), 105 + AisleNumber: stringPtr(" 12 "), 106 + Brand: stringPtr(" Orchard Co "), 107 + Description: stringPtr(" Honeycrisp Apple "), 108 + Size: stringPtr(" 3 lb "), 109 + PriceRegular: &regular, 110 + PriceSale: &sale, 111 + Categories: &categories, 112 + }, 0) 113 + 114 + if ingredient.ProductID != "apple-1" { 115 + t.Fatalf("unexpected product id: %+v", ingredient) 116 + } 117 + if ingredient.AisleNumber != "12" || ingredient.Brand != "Orchard Co" || ingredient.Description != "Honeycrisp Apple" || ingredient.Size != "3 lb" { 118 + t.Fatalf("unexpected normalized ingredient: %+v", ingredient) 119 + } 120 + if ingredient.PriceRegular == nil || *ingredient.PriceRegular != regular { 121 + t.Fatalf("unexpected regular price: %+v", ingredient.PriceRegular) 122 + } 123 + if ingredient.PriceSale == nil || *ingredient.PriceSale != sale { 124 + t.Fatalf("unexpected sale price: %+v", ingredient.PriceSale) 125 + } 126 + if !slices.Equal(ingredient.Categories, categories) { 127 + t.Fatalf("unexpected categories: got %v want %v", ingredient.Categories, categories) 128 + } 129 + } 130 + 131 + func stringPtr(value string) *string { 132 + return &value 133 + }
-3346
internal/kroger/swagger.yaml
··· 1 - openapi: 3.0.3 2 - info: 3 - title: Kroger Public APIs 4 - description: >- 5 - The following APIs are publicly available for all clients to build new 6 - products, 7 - 8 - services, or customer experiences that leverage the unique data, functions, 9 - and 10 - 11 - applications of Kroger. As a company that strives to empower the developer 12 - community 13 - 14 - and meet our customers where they are, we are offering these APIs as the 15 - first 16 - 17 - step to building an ecosystem that promotes speed, simplicity, and security. 18 - 19 - 20 - 21 - https://developer.kroger.com/reference 22 - version: 1.0.0 23 - contact: {} 24 - components: 25 - securitySchemes: 26 - OAuth2AuthorizationCode: 27 - type: oauth2 28 - flows: 29 - authorizationCode: 30 - authorizationUrl: https://api.kroger.com/v1/connect/oauth2/authorize 31 - # Certificatn authorizationUrl: https://api-ce.kroger.com/v1/connect/oauth2/authorize 32 - tokenUrl: https://api.kroger.com/v1/connect/oauth2/token 33 - # Certification tokenUrl: https://api-ce.kroger.com/v1/connect/oauth2/token 34 - scopes: 35 - profile.compact: Grants read access to a customer profile ID. 36 - cart.basic:write: Grants write access to a customer's cart. 37 - product.compact: Grants read access to general product information. 38 - N/A: No scope required. 39 - 40 - OAuth2ClientCreds: 41 - type: oauth2 42 - flows: 43 - clientCredentials: 44 - tokenUrl: https://api.kroger.com/v1/connect/oauth2/token 45 - # Certification tokenUrl: https://api-ce.kroger.com/v1/connect/oauth2/token 46 - scopes: 47 - product.compact: Grants read access to general product information. 48 - servers: 49 - - url: https://api.kroger.com/v1/ 50 - description: Production server (uses live data) 51 - # - url: https://api-ce.kroger.com/v1/ 52 - # description: Certification Server 53 - paths: 54 - /identity/profile: 55 - get: 56 - security: 57 - - OAuth2ClientCreds: 58 - - profile.compact 59 - tags: 60 - - Identity 61 - summary: User profile information 62 - description: >- 63 - Provides access to the profile `id` of an authenticated customer. 64 - <br><br> **Note**: the customer must be authenticated using the OAuth2 65 - Authorization Code grant type. 66 - operationId: userProfileInformation 67 - responses: 68 - '200': 69 - description: OK 70 - content: 71 - application/json: 72 - schema: 73 - type: object 74 - properties: 75 - data: 76 - type: object 77 - properties: 78 - id: 79 - type: object 80 - properties: {} 81 - meta: 82 - type: object 83 - properties: 84 - pagination: 85 - type: object 86 - properties: 87 - limit: 88 - type: number 89 - example: <number> 90 - start: 91 - type: number 92 - example: <number> 93 - total: 94 - type: number 95 - example: <number> 96 - warnings: 97 - type: array 98 - items: 99 - type: string 100 - example: <string> 101 - example: 102 - - <string> 103 - - <string> 104 - examples: 105 - OK: 106 - value: 107 - data: 108 - id: {} 109 - meta: 110 - pagination: 111 - limit: <number> 112 - start: <number> 113 - total: <number> 114 - warnings: 115 - - <string> 116 - - <string> 117 - '401': 118 - description: Unauthorized 119 - content: 120 - application/json: 121 - schema: 122 - type: object 123 - properties: 124 - errors: 125 - type: object 126 - properties: 127 - error: 128 - type: string 129 - example: <string> 130 - error_description: 131 - type: string 132 - example: <string> 133 - examples: 134 - Unauthorized: 135 - value: 136 - errors: 137 - error: <string> 138 - error_description: <string> 139 - '403': 140 - description: Forbidden 141 - content: 142 - application/json: 143 - schema: 144 - type: object 145 - properties: 146 - errors: 147 - type: object 148 - properties: 149 - code: 150 - type: string 151 - example: <string> 152 - reason: 153 - type: string 154 - example: <string> 155 - timestamp: 156 - type: number 157 - example: <number> 158 - examples: 159 - Forbidden: 160 - value: 161 - errors: 162 - code: <string> 163 - reason: <string> 164 - timestamp: <number> 165 - '404': 166 - description: Not Found 167 - content: 168 - application/octet-stream: 169 - examples: 170 - Not Found: 171 - value: '"<object>"' 172 - '500': 173 - description: Internal Server Error 174 - content: 175 - application/json: 176 - schema: 177 - type: object 178 - properties: 179 - errors: 180 - type: object 181 - properties: 182 - code: 183 - type: string 184 - example: <string> 185 - reason: 186 - type: string 187 - example: <string> 188 - timestamp: 189 - type: number 190 - example: <number> 191 - examples: 192 - Internal Server Error: 193 - value: 194 - errors: 195 - code: <string> 196 - reason: <string> 197 - timestamp: <number> 198 - /cart/add: 199 - put: 200 - security: 201 - - OAuth2AuthorizationCode: 202 - - cart.basic:write 203 - tags: 204 - - Cart 205 - summary: Add to cart 206 - description: >- 207 - Provides you with access to add items to the cart of an authenticated 208 - customer. <br><br> **Note**: the customer must be authenticated using 209 - the OAuth2 Authorization Code grant type. 210 - operationId: addToCart 211 - requestBody: 212 - content: 213 - application/json: 214 - schema: 215 - type: object 216 - properties: 217 - items: 218 - type: array 219 - items: 220 - type: object 221 - properties: 222 - modality: 223 - type: string 224 - example: <string> 225 - quantity: 226 - type: number 227 - example: 1 228 - upc: 229 - type: string 230 - example: '0001111040101' 231 - example: 232 - - modality: <string> 233 - quantity: 1 234 - upc: '0001111040101' 235 - example: 236 - items: 237 - - modality: <string> 238 - quantity: 1 239 - upc: '0001111040101' 240 - responses: 241 - '204': 242 - description: OK 243 - content: 244 - application/octet-stream: 245 - examples: 246 - OK: 247 - value: '"<object>"' 248 - '400': 249 - description: Bad Request 250 - content: 251 - application/json: 252 - schema: 253 - type: object 254 - properties: 255 - errors: 256 - type: string 257 - example: <object> 258 - examples: 259 - Bad Request: 260 - value: 261 - errors: <object> 262 - '401': 263 - description: Unauthorized 264 - content: 265 - application/json: 266 - schema: 267 - type: object 268 - properties: 269 - errors: 270 - type: object 271 - properties: 272 - error: 273 - type: string 274 - example: <string> 275 - error_description: 276 - type: string 277 - example: <string> 278 - examples: 279 - Unauthorized: 280 - value: 281 - errors: 282 - error: <string> 283 - error_description: <string> 284 - '500': 285 - description: Internal Server Error 286 - content: 287 - application/json: 288 - schema: 289 - type: object 290 - properties: 291 - errors: 292 - type: object 293 - properties: 294 - code: 295 - type: string 296 - example: <string> 297 - reason: 298 - type: string 299 - example: <string> 300 - timestamp: 301 - type: number 302 - example: <number> 303 - examples: 304 - Internal Server Error: 305 - value: 306 - errors: 307 - code: <string> 308 - reason: <string> 309 - timestamp: <number> 310 - /products: 311 - get: 312 - security: 313 - - OAuth2AuthorizationCode: 314 - - product.compact 315 - - OAuth2ClientCreds: 316 - - product.compact 317 - tags: 318 - - Products 319 - summary: Product Search 320 - description: >- 321 - Allows you to find products by passing in either a search term or product Id. 322 - 323 - Initial Search Value Required 324 - An initial search value is requred for all requests. You can use either of the following parameters as an initial search value: 325 - 326 - filter.term - When using the term parameter, the API performs a fuzzy search based on the term provided in the string. Search results are based on how relevant the term is to the product description. 327 - 328 - filter.productId - When using the productId parameter, the API performs a query to find an exact match. 329 - operationId: productSearch 330 - parameters: 331 - - name: filter.term 332 - in: query 333 - schema: 334 - type: string 335 - example: <string> 336 - description: >- 337 - A search term to filter product results. As an example, you could 338 - input milk, bread, or salt. 339 - - name: filter.locationId 340 - in: query 341 - schema: 342 - type: string 343 - example: <string> 344 - description: >- 345 - The locationId of the store you want results limited to. When using 346 - this filter, only products available at that location are returned. 347 - - name: filter.productId 348 - in: query 349 - schema: 350 - type: string 351 - example: <string> 352 - description: >- 353 - The productId of the products(s) you want returned. For more than 354 - one item, the list must be comma-separated. When used, all other 355 - query parameters are ignored. 356 - - name: filter.brand 357 - in: query 358 - schema: 359 - type: string 360 - example: <string> 361 - description: >- 362 - The brand name of the product(s) you want returned. When using this 363 - filter, only products by that brand are returned. Brand names are 364 - case-sensitive, and lists must be pipe-separated. 365 - - name: filter.fulfillment 366 - in: query 367 - schema: 368 - type: string 369 - example: <string> 370 - description: >- 371 - The available fulfillment types of the product(s) you want returned. 372 - Fulfillment types are case-sensitive, and lists must be 373 - comma-separated. Must be one or more of the follow types: ais - 374 - Available In Store, csp - Curbside Pickup, dth - Delivery To Home, 375 - sth - Ship To Home 376 - - name: filter.start 377 - in: query 378 - schema: 379 - type: string 380 - example: <string> 381 - description: The number of products you want to skip. 382 - - name: filter.limit 383 - in: query 384 - schema: 385 - type: string 386 - example: <integer> 387 - description: The number of products you want returned. 388 - responses: 389 - '200': 390 - description: OK 391 - content: 392 - application/json: 393 - schema: 394 - type: object 395 - properties: 396 - data: 397 - type: array 398 - items: 399 - type: object 400 - properties: 401 - aisleLocations: 402 - type: array 403 - items: 404 - type: object 405 - properties: 406 - bayNumber: 407 - type: string 408 - example: <string> 409 - description: 410 - type: string 411 - example: <string> 412 - number: 413 - type: string 414 - example: <string> 415 - numberOfFacings: 416 - type: string 417 - example: <string> 418 - sequenceNumber: 419 - type: string 420 - example: <string> 421 - shelfNumber: 422 - type: string 423 - example: <string> 424 - shelfPositionInBay: 425 - type: string 426 - example: <string> 427 - side: 428 - type: string 429 - example: <string> 430 - example: 431 - - bayNumber: <string> 432 - description: <string> 433 - number: <string> 434 - numberOfFacings: <string> 435 - sequenceNumber: <string> 436 - shelfNumber: <string> 437 - shelfPositionInBay: <string> 438 - side: <string> 439 - - bayNumber: <string> 440 - description: <string> 441 - number: <string> 442 - numberOfFacings: <string> 443 - sequenceNumber: <string> 444 - shelfNumber: <string> 445 - shelfPositionInBay: <string> 446 - side: <string> 447 - brand: 448 - type: string 449 - example: <string> 450 - categories: 451 - type: array 452 - items: 453 - type: string 454 - example: <string> 455 - example: 456 - - <string> 457 - - <string> 458 - countryOrigin: 459 - type: string 460 - example: <string> 461 - description: 462 - type: string 463 - example: <string> 464 - images: 465 - type: array 466 - items: 467 - type: object 468 - properties: 469 - default: 470 - type: boolean 471 - example: <boolean> 472 - type: 473 - type: string 474 - example: boolean 475 - example: 476 - - default: <boolean> 477 - type: boolean 478 - - default: <boolean> 479 - type: boolean 480 - itemInformation: 481 - type: object 482 - properties: 483 - depth: 484 - type: string 485 - example: <string> 486 - height: 487 - type: string 488 - example: <string> 489 - width: 490 - type: string 491 - example: <string> 492 - items: 493 - type: array 494 - items: 495 - type: object 496 - properties: 497 - favorite: 498 - type: boolean 499 - example: <boolean> 500 - fulfillment: 501 - type: object 502 - properties: 503 - curbside: 504 - type: boolean 505 - example: <boolean> 506 - delivery: 507 - type: boolean 508 - example: <boolean> 509 - inventory: 510 - type: object 511 - properties: 512 - stockLevel: 513 - type: string 514 - example: <string> 515 - itemId: 516 - type: string 517 - example: <string> 518 - price: 519 - type: object 520 - properties: 521 - promo: 522 - type: number 523 - example: <number> 524 - regular: 525 - type: number 526 - example: <number> 527 - size: 528 - type: string 529 - example: <string> 530 - example: 531 - - favorite: <boolean> 532 - fulfillment: 533 - curbside: <boolean> 534 - delivery: <boolean> 535 - inventory: 536 - stockLevel: <string> 537 - itemId: <string> 538 - price: 539 - promo: <number> 540 - regular: <number> 541 - size: <string> 542 - - favorite: <boolean> 543 - fulfillment: 544 - curbside: <boolean> 545 - delivery: <boolean> 546 - inventory: 547 - stockLevel: <string> 548 - itemId: <string> 549 - price: 550 - promo: <number> 551 - regular: <number> 552 - size: <string> 553 - productId: 554 - type: string 555 - example: <string> 556 - temperature: 557 - type: object 558 - properties: 559 - heatSensitive: 560 - type: boolean 561 - example: <boolean> 562 - indicator: 563 - type: string 564 - example: <string> 565 - upc: 566 - type: string 567 - example: <string> 568 - example: 569 - - aisleLocations: 570 - - bayNumber: <string> 571 - description: <string> 572 - number: <string> 573 - numberOfFacings: <string> 574 - sequenceNumber: <string> 575 - shelfNumber: <string> 576 - shelfPositionInBay: <string> 577 - side: <string> 578 - - bayNumber: <string> 579 - description: <string> 580 - number: <string> 581 - numberOfFacings: <string> 582 - sequenceNumber: <string> 583 - shelfNumber: <string> 584 - shelfPositionInBay: <string> 585 - side: <string> 586 - brand: <string> 587 - categories: 588 - - <string> 589 - - <string> 590 - countryOrigin: <string> 591 - description: <string> 592 - images: 593 - - default: <boolean> 594 - type: boolean 595 - - default: <boolean> 596 - type: boolean 597 - itemInformation: 598 - depth: <string> 599 - height: <string> 600 - width: <string> 601 - items: 602 - - favorite: <boolean> 603 - fulfillment: 604 - curbside: <boolean> 605 - delivery: <boolean> 606 - inventory: 607 - stockLevel: <string> 608 - itemId: <string> 609 - price: 610 - promo: <number> 611 - regular: <number> 612 - size: <string> 613 - - favorite: <boolean> 614 - fulfillment: 615 - curbside: <boolean> 616 - delivery: <boolean> 617 - inventory: 618 - stockLevel: <string> 619 - itemId: <string> 620 - price: 621 - promo: <number> 622 - regular: <number> 623 - size: <string> 624 - productId: <string> 625 - temperature: 626 - heatSensitive: <boolean> 627 - indicator: <string> 628 - upc: <string> 629 - - aisleLocations: 630 - - bayNumber: <string> 631 - description: <string> 632 - number: <string> 633 - numberOfFacings: <string> 634 - sequenceNumber: <string> 635 - shelfNumber: <string> 636 - shelfPositionInBay: <string> 637 - side: <string> 638 - - bayNumber: <string> 639 - description: <string> 640 - number: <string> 641 - numberOfFacings: <string> 642 - sequenceNumber: <string> 643 - shelfNumber: <string> 644 - shelfPositionInBay: <string> 645 - side: <string> 646 - brand: <string> 647 - categories: 648 - - <string> 649 - - <string> 650 - countryOrigin: <string> 651 - description: <string> 652 - images: 653 - - default: <boolean> 654 - type: boolean 655 - - default: <boolean> 656 - type: boolean 657 - itemInformation: 658 - depth: <string> 659 - height: <string> 660 - width: <string> 661 - items: 662 - - favorite: <boolean> 663 - fulfillment: 664 - curbside: <boolean> 665 - delivery: <boolean> 666 - inventory: 667 - stockLevel: <string> 668 - itemId: <string> 669 - price: 670 - promo: <number> 671 - regular: <number> 672 - size: <string> 673 - productId: <string> 674 - temperature: 675 - heatSensitive: <boolean> 676 - indicator: <string> 677 - upc: <string> 678 - meta: 679 - type: object 680 - properties: 681 - pagination: 682 - type: object 683 - properties: 684 - limit: 685 - type: number 686 - example: <number> 687 - start: 688 - type: number 689 - example: <number> 690 - total: 691 - type: number 692 - example: <number> 693 - warnings: 694 - type: array 695 - items: 696 - type: string 697 - example: <string> 698 - example: 699 - - <string> 700 - - <string> 701 - examples: 702 - OK: 703 - value: 704 - data: 705 - - aisleLocations: 706 - - bayNumber: <string> 707 - description: <string> 708 - number: <string> 709 - numberOfFacings: <string> 710 - sequenceNumber: <string> 711 - shelfNumber: <string> 712 - shelfPositionInBay: <string> 713 - side: <string> 714 - - bayNumber: <string> 715 - description: <string> 716 - number: <string> 717 - numberOfFacings: <string> 718 - sequenceNumber: <string> 719 - shelfNumber: <string> 720 - shelfPositionInBay: <string> 721 - side: <string> 722 - brand: <string> 723 - categories: 724 - - <string> 725 - - <string> 726 - countryOrigin: <string> 727 - description: <string> 728 - images: 729 - - default: <boolean> 730 - type: boolean 731 - - default: <boolean> 732 - type: boolean 733 - itemInformation: 734 - depth: <string> 735 - height: <string> 736 - width: <string> 737 - items: 738 - - favorite: <boolean> 739 - fulfillment: 740 - curbside: <boolean> 741 - delivery: <boolean> 742 - inventory: 743 - stockLevel: <string> 744 - itemId: <string> 745 - price: 746 - promo: <number> 747 - regular: <number> 748 - size: <string> 749 - - favorite: <boolean> 750 - fulfillment: 751 - curbside: <boolean> 752 - delivery: <boolean> 753 - inventory: 754 - stockLevel: <string> 755 - itemId: <string> 756 - price: 757 - promo: <number> 758 - regular: <number> 759 - size: <string> 760 - productId: <string> 761 - temperature: 762 - heatSensitive: <boolean> 763 - indicator: <string> 764 - upc: <string> 765 - - aisleLocations: 766 - - bayNumber: <string> 767 - description: <string> 768 - number: <string> 769 - numberOfFacings: <string> 770 - sequenceNumber: <string> 771 - shelfNumber: <string> 772 - shelfPositionInBay: <string> 773 - side: <string> 774 - - bayNumber: <string> 775 - description: <string> 776 - number: <string> 777 - numberOfFacings: <string> 778 - sequenceNumber: <string> 779 - shelfNumber: <string> 780 - shelfPositionInBay: <string> 781 - side: <string> 782 - brand: <string> 783 - categories: 784 - - <string> 785 - - <string> 786 - countryOrigin: <string> 787 - description: <string> 788 - images: 789 - - default: <boolean> 790 - type: boolean 791 - - default: <boolean> 792 - type: boolean 793 - itemInformation: 794 - depth: <string> 795 - height: <string> 796 - width: <string> 797 - items: 798 - - favorite: <boolean> 799 - fulfillment: 800 - curbside: <boolean> 801 - delivery: <boolean> 802 - inventory: 803 - stockLevel: <string> 804 - itemId: <string> 805 - price: 806 - promo: <number> 807 - regular: <number> 808 - size: <string> 809 - productId: <string> 810 - temperature: 811 - heatSensitive: <boolean> 812 - indicator: <string> 813 - upc: <string> 814 - meta: 815 - pagination: 816 - limit: <number> 817 - start: <number> 818 - total: <number> 819 - warnings: 820 - - <string> 821 - - <string> 822 - '400': 823 - description: Bad Request 824 - content: 825 - application/json: 826 - schema: 827 - type: object 828 - properties: 829 - errors: 830 - type: string 831 - example: <object> 832 - examples: 833 - Bad Request: 834 - value: 835 - errors: <object> 836 - '401': 837 - description: Unauthorized 838 - content: 839 - application/json: 840 - schema: 841 - type: object 842 - properties: 843 - errors: 844 - type: object 845 - properties: 846 - error: 847 - type: string 848 - example: <string> 849 - error_description: 850 - type: string 851 - example: <string> 852 - examples: 853 - Unauthorized: 854 - value: 855 - errors: 856 - error: <string> 857 - error_description: <string> 858 - '500': 859 - description: Internal Server Error 860 - content: 861 - application/json: 862 - schema: 863 - type: object 864 - properties: 865 - errors: 866 - type: object 867 - properties: 868 - code: 869 - type: string 870 - example: <string> 871 - reason: 872 - type: string 873 - example: <string> 874 - timestamp: 875 - type: number 876 - example: <number> 877 - examples: 878 - Internal Server Error: 879 - value: 880 - errors: 881 - code: <string> 882 - reason: <string> 883 - timestamp: <number> 884 - /products/{id}: 885 - get: 886 - security: 887 - - OAuth2AuthorizationCode: 888 - - product.compact 889 - - OAuth2ClientCreds: 890 - - product.compact 891 - tags: 892 - - Products 893 - summary: Product details 894 - description: >- 895 - Provides access to the details of a specific product by either using the 896 - `productId` or `UPC`. To return the product price and aisle location, 897 - you must include the `filter.locationId` query parameters. 898 - operationId: productDetails 899 - parameters: 900 - - name: filter.locationId 901 - in: query 902 - schema: 903 - type: string 904 - example: <string> 905 - description: >- 906 - The locationId of the store you want results limited to. When using 907 - this filter, only products available at that location are returned. 908 - responses: 909 - '200': 910 - description: OK 911 - content: 912 - application/json: 913 - schema: 914 - type: object 915 - properties: 916 - data: 917 - type: object 918 - properties: 919 - aisleLocations: 920 - type: array 921 - items: 922 - type: object 923 - properties: 924 - bayNumber: 925 - type: string 926 - example: <string> 927 - description: 928 - type: string 929 - example: <string> 930 - number: 931 - type: string 932 - example: <string> 933 - numberOfFacings: 934 - type: string 935 - example: <string> 936 - sequenceNumber: 937 - type: string 938 - example: <string> 939 - shelfNumber: 940 - type: string 941 - example: <string> 942 - shelfPositionInBay: 943 - type: string 944 - example: <string> 945 - side: 946 - type: string 947 - example: <string> 948 - example: 949 - - bayNumber: <string> 950 - description: <string> 951 - number: <string> 952 - numberOfFacings: <string> 953 - sequenceNumber: <string> 954 - shelfNumber: <string> 955 - shelfPositionInBay: <string> 956 - side: <string> 957 - - bayNumber: <string> 958 - description: <string> 959 - number: <string> 960 - numberOfFacings: <string> 961 - sequenceNumber: <string> 962 - shelfNumber: <string> 963 - shelfPositionInBay: <string> 964 - side: <string> 965 - brand: 966 - type: string 967 - example: <string> 968 - categories: 969 - type: array 970 - items: 971 - type: string 972 - example: <string> 973 - example: 974 - - <string> 975 - - <string> 976 - countryOrigin: 977 - type: string 978 - example: <string> 979 - description: 980 - type: string 981 - example: <string> 982 - images: 983 - type: array 984 - items: 985 - type: object 986 - properties: 987 - default: 988 - type: boolean 989 - example: <boolean> 990 - type: 991 - type: string 992 - example: boolean 993 - example: 994 - - default: <boolean> 995 - type: boolean 996 - - default: <boolean> 997 - type: boolean 998 - itemInformation: 999 - type: object 1000 - properties: 1001 - depth: 1002 - type: string 1003 - example: <string> 1004 - height: 1005 - type: string 1006 - example: <string> 1007 - width: 1008 - type: string 1009 - example: <string> 1010 - items: 1011 - type: array 1012 - items: 1013 - type: object 1014 - properties: 1015 - favorite: 1016 - type: boolean 1017 - example: <boolean> 1018 - fulfillment: 1019 - type: object 1020 - properties: 1021 - curbside: 1022 - type: boolean 1023 - example: <boolean> 1024 - delivery: 1025 - type: boolean 1026 - example: <boolean> 1027 - inventory: 1028 - type: object 1029 - properties: 1030 - stockLevel: 1031 - type: string 1032 - example: <string> 1033 - itemId: 1034 - type: string 1035 - example: <string> 1036 - price: 1037 - type: object 1038 - properties: 1039 - promo: 1040 - type: number 1041 - example: <number> 1042 - regular: 1043 - type: number 1044 - example: <number> 1045 - size: 1046 - type: string 1047 - example: <string> 1048 - example: 1049 - - favorite: <boolean> 1050 - fulfillment: 1051 - curbside: <boolean> 1052 - delivery: <boolean> 1053 - inventory: 1054 - stockLevel: <string> 1055 - itemId: <string> 1056 - price: 1057 - promo: <number> 1058 - regular: <number> 1059 - size: <string> 1060 - productId: 1061 - type: string 1062 - example: <string> 1063 - temperature: 1064 - type: object 1065 - properties: 1066 - heatSensitive: 1067 - type: boolean 1068 - example: <boolean> 1069 - indicator: 1070 - type: string 1071 - example: <string> 1072 - upc: 1073 - type: string 1074 - example: <string> 1075 - meta: 1076 - type: object 1077 - properties: 1078 - pagination: 1079 - type: object 1080 - properties: 1081 - limit: 1082 - type: number 1083 - example: <number> 1084 - start: 1085 - type: number 1086 - example: <number> 1087 - total: 1088 - type: number 1089 - example: <number> 1090 - warnings: 1091 - type: array 1092 - items: 1093 - type: string 1094 - example: <string> 1095 - example: 1096 - - <string> 1097 - - <string> 1098 - examples: 1099 - OK: 1100 - value: 1101 - data: 1102 - aisleLocations: 1103 - - bayNumber: <string> 1104 - description: <string> 1105 - number: <string> 1106 - numberOfFacings: <string> 1107 - sequenceNumber: <string> 1108 - shelfNumber: <string> 1109 - shelfPositionInBay: <string> 1110 - side: <string> 1111 - - bayNumber: <string> 1112 - description: <string> 1113 - number: <string> 1114 - numberOfFacings: <string> 1115 - sequenceNumber: <string> 1116 - shelfNumber: <string> 1117 - shelfPositionInBay: <string> 1118 - side: <string> 1119 - brand: <string> 1120 - categories: 1121 - - <string> 1122 - - <string> 1123 - countryOrigin: <string> 1124 - description: <string> 1125 - images: 1126 - - default: <boolean> 1127 - type: boolean 1128 - - default: <boolean> 1129 - type: boolean 1130 - itemInformation: 1131 - depth: <string> 1132 - height: <string> 1133 - width: <string> 1134 - items: 1135 - - favorite: <boolean> 1136 - fulfillment: 1137 - curbside: <boolean> 1138 - delivery: <boolean> 1139 - inventory: 1140 - stockLevel: <string> 1141 - itemId: <string> 1142 - price: 1143 - promo: <number> 1144 - regular: <number> 1145 - size: <string> 1146 - productId: <string> 1147 - temperature: 1148 - heatSensitive: <boolean> 1149 - indicator: <string> 1150 - upc: <string> 1151 - meta: 1152 - pagination: 1153 - limit: <number> 1154 - start: <number> 1155 - total: <number> 1156 - warnings: 1157 - - <string> 1158 - - <string> 1159 - '400': 1160 - description: Bad Request 1161 - content: 1162 - application/json: 1163 - schema: 1164 - type: object 1165 - properties: 1166 - errors: 1167 - type: object 1168 - properties: 1169 - code: 1170 - type: string 1171 - example: <string> 1172 - reason: 1173 - type: string 1174 - example: <string> 1175 - timestamp: 1176 - type: number 1177 - example: <number> 1178 - examples: 1179 - Bad Request: 1180 - value: 1181 - errors: 1182 - code: <string> 1183 - reason: <string> 1184 - timestamp: <number> 1185 - '401': 1186 - description: Unauthorized 1187 - content: 1188 - application/json: 1189 - schema: 1190 - type: object 1191 - properties: 1192 - errors: 1193 - type: object 1194 - properties: 1195 - error: 1196 - type: string 1197 - example: <string> 1198 - error_description: 1199 - type: string 1200 - example: <string> 1201 - examples: 1202 - Unauthorized: 1203 - value: 1204 - errors: 1205 - error: <string> 1206 - error_description: <string> 1207 - '404': 1208 - description: Not Found 1209 - content: 1210 - application/octet-stream: 1211 - examples: 1212 - Not Found: 1213 - value: '"<object>"' 1214 - '500': 1215 - description: Internal Server Error 1216 - content: 1217 - application/json: 1218 - schema: 1219 - type: object 1220 - properties: 1221 - errors: 1222 - type: object 1223 - properties: 1224 - code: 1225 - type: string 1226 - example: <string> 1227 - reason: 1228 - type: string 1229 - example: <string> 1230 - timestamp: 1231 - type: number 1232 - example: <number> 1233 - examples: 1234 - Internal Server Error: 1235 - value: 1236 - errors: 1237 - code: <string> 1238 - reason: <string> 1239 - timestamp: <number> 1240 - parameters: 1241 - - name: id 1242 - in: path 1243 - required: true 1244 - schema: 1245 - type: string 1246 - example: '' 1247 - description: Either the productId or UPC of the product you want returned. 1248 - /locations/{locationId}: 1249 - get: 1250 - security: 1251 - - OAuth2AuthorizationCode: [] 1252 - - OAuth2ClientCreds: [] 1253 - tags: 1254 - - Locations 1255 - summary: Location details 1256 - description: >- 1257 - Provides access to the details of a specific location by using the 1258 - `locationId`. 1259 - operationId: locationDetails 1260 - responses: 1261 - '200': 1262 - description: OK 1263 - content: 1264 - application/json: 1265 - schema: 1266 - type: object 1267 - properties: 1268 - data: 1269 - type: object 1270 - properties: 1271 - address: 1272 - type: object 1273 - properties: 1274 - addressLine1: 1275 - type: string 1276 - example: <string> 1277 - addressLine2: 1278 - type: string 1279 - example: <string> 1280 - city: 1281 - type: string 1282 - example: <string> 1283 - county: 1284 - type: string 1285 - example: <string> 1286 - state: 1287 - type: string 1288 - example: <string> 1289 - zipCode: 1290 - type: string 1291 - example: <string> 1292 - chain: 1293 - type: string 1294 - example: <string> 1295 - departments: 1296 - type: array 1297 - items: 1298 - type: object 1299 - properties: 1300 - departmentId: 1301 - type: string 1302 - example: <string> 1303 - hours: 1304 - type: object 1305 - properties: 1306 - Open24: 1307 - type: boolean 1308 - example: <boolean> 1309 - friday: 1310 - type: object 1311 - properties: 1312 - close: 1313 - type: string 1314 - example: <string> 1315 - open: 1316 - type: string 1317 - example: <string> 1318 - open24: 1319 - type: boolean 1320 - example: <boolean> 1321 - monday: 1322 - type: object 1323 - properties: 1324 - close: 1325 - type: string 1326 - example: <string> 1327 - open: 1328 - type: string 1329 - example: <string> 1330 - open24: 1331 - type: boolean 1332 - example: <boolean> 1333 - saturday: 1334 - type: object 1335 - properties: 1336 - close: 1337 - type: string 1338 - example: <string> 1339 - open: 1340 - type: string 1341 - example: <string> 1342 - open24: 1343 - type: boolean 1344 - example: <boolean> 1345 - sunday: 1346 - type: object 1347 - properties: 1348 - close: 1349 - type: string 1350 - example: <string> 1351 - open: 1352 - type: string 1353 - example: <string> 1354 - open24: 1355 - type: boolean 1356 - example: <boolean> 1357 - thursday: 1358 - type: object 1359 - properties: 1360 - close: 1361 - type: string 1362 - example: <string> 1363 - open: 1364 - type: string 1365 - example: <string> 1366 - open24: 1367 - type: boolean 1368 - example: <boolean> 1369 - tuesday: 1370 - type: object 1371 - properties: 1372 - close: 1373 - type: string 1374 - example: <string> 1375 - open: 1376 - type: string 1377 - example: <string> 1378 - open24: 1379 - type: boolean 1380 - example: <boolean> 1381 - wednesday: 1382 - type: object 1383 - properties: 1384 - close: 1385 - type: string 1386 - example: <string> 1387 - open: 1388 - type: string 1389 - example: <string> 1390 - open24: 1391 - type: boolean 1392 - example: <boolean> 1393 - name: 1394 - type: string 1395 - example: <string> 1396 - phone: 1397 - type: string 1398 - example: <string> 1399 - example: 1400 - - departmentId: <string> 1401 - hours: 1402 - Open24: <boolean> 1403 - friday: 1404 - close: <string> 1405 - open: <string> 1406 - open24: <boolean> 1407 - monday: 1408 - close: <string> 1409 - open: <string> 1410 - open24: <boolean> 1411 - saturday: 1412 - close: <string> 1413 - open: <string> 1414 - open24: <boolean> 1415 - sunday: 1416 - close: <string> 1417 - open: <string> 1418 - open24: <boolean> 1419 - thursday: 1420 - close: <string> 1421 - open: <string> 1422 - open24: <boolean> 1423 - tuesday: 1424 - close: <string> 1425 - open: <string> 1426 - open24: <boolean> 1427 - wednesday: 1428 - close: <string> 1429 - open: <string> 1430 - open24: <boolean> 1431 - name: <string> 1432 - phone: <string> 1433 - - departmentId: <string> 1434 - hours: 1435 - Open24: <boolean> 1436 - friday: 1437 - close: <string> 1438 - open: <string> 1439 - open24: <boolean> 1440 - monday: 1441 - close: <string> 1442 - open: <string> 1443 - open24: <boolean> 1444 - saturday: 1445 - close: <string> 1446 - open: <string> 1447 - open24: <boolean> 1448 - sunday: 1449 - close: <string> 1450 - open: <string> 1451 - open24: <boolean> 1452 - thursday: 1453 - close: <string> 1454 - open: <string> 1455 - open24: <boolean> 1456 - tuesday: 1457 - close: <string> 1458 - open: <string> 1459 - open24: <boolean> 1460 - wednesday: 1461 - close: <string> 1462 - open: <string> 1463 - open24: <boolean> 1464 - name: <string> 1465 - phone: <string> 1466 - divisionNumber: 1467 - type: string 1468 - example: <string> 1469 - geolocation: 1470 - type: object 1471 - properties: 1472 - latLng: 1473 - type: string 1474 - example: <string> 1475 - latitude: 1476 - type: number 1477 - example: <number> 1478 - longitude: 1479 - type: number 1480 - example: <number> 1481 - hours: 1482 - type: object 1483 - properties: 1484 - Open24: 1485 - type: boolean 1486 - example: <boolean> 1487 - friday: 1488 - type: object 1489 - properties: 1490 - close: 1491 - type: string 1492 - example: <string> 1493 - open: 1494 - type: string 1495 - example: <string> 1496 - open24: 1497 - type: boolean 1498 - example: <boolean> 1499 - gmtOffset: 1500 - type: string 1501 - example: <string> 1502 - monday: 1503 - type: object 1504 - properties: 1505 - close: 1506 - type: string 1507 - example: <string> 1508 - open: 1509 - type: string 1510 - example: <string> 1511 - open24: 1512 - type: boolean 1513 - example: <boolean> 1514 - saturday: 1515 - type: object 1516 - properties: 1517 - close: 1518 - type: string 1519 - example: <string> 1520 - open: 1521 - type: string 1522 - example: <string> 1523 - open24: 1524 - type: boolean 1525 - example: <boolean> 1526 - sunday: 1527 - type: object 1528 - properties: 1529 - close: 1530 - type: string 1531 - example: <string> 1532 - open: 1533 - type: string 1534 - example: <string> 1535 - open24: 1536 - type: boolean 1537 - example: <boolean> 1538 - thursday: 1539 - type: object 1540 - properties: 1541 - close: 1542 - type: string 1543 - example: <string> 1544 - open: 1545 - type: string 1546 - example: <string> 1547 - open24: 1548 - type: boolean 1549 - example: <boolean> 1550 - timezone: 1551 - type: string 1552 - example: <string> 1553 - tuesday: 1554 - type: object 1555 - properties: 1556 - close: 1557 - type: string 1558 - example: <string> 1559 - open: 1560 - type: string 1561 - example: <string> 1562 - open24: 1563 - type: boolean 1564 - example: <boolean> 1565 - wednesday: 1566 - type: object 1567 - properties: 1568 - close: 1569 - type: string 1570 - example: <string> 1571 - open: 1572 - type: string 1573 - example: <string> 1574 - open24: 1575 - type: boolean 1576 - example: <boolean> 1577 - locationId: 1578 - type: string 1579 - example: <string> 1580 - name: 1581 - type: string 1582 - example: <string> 1583 - phone: 1584 - type: string 1585 - example: <string> 1586 - storeNumber: 1587 - type: string 1588 - example: <string> 1589 - meta: 1590 - type: object 1591 - properties: 1592 - pagination: 1593 - type: object 1594 - properties: 1595 - limit: 1596 - type: number 1597 - example: <number> 1598 - start: 1599 - type: number 1600 - example: <number> 1601 - total: 1602 - type: number 1603 - example: <number> 1604 - warnings: 1605 - type: array 1606 - items: 1607 - type: string 1608 - example: <string> 1609 - example: 1610 - - <string> 1611 - - <string> 1612 - examples: 1613 - OK: 1614 - value: 1615 - data: 1616 - address: 1617 - addressLine1: <string> 1618 - addressLine2: <string> 1619 - city: <string> 1620 - county: <string> 1621 - state: <string> 1622 - zipCode: <string> 1623 - chain: <string> 1624 - departments: 1625 - - departmentId: <string> 1626 - hours: 1627 - Open24: <boolean> 1628 - friday: 1629 - close: <string> 1630 - open: <string> 1631 - open24: <boolean> 1632 - monday: 1633 - close: <string> 1634 - open: <string> 1635 - open24: <boolean> 1636 - saturday: 1637 - close: <string> 1638 - open: <string> 1639 - open24: <boolean> 1640 - sunday: 1641 - close: <string> 1642 - open: <string> 1643 - open24: <boolean> 1644 - thursday: 1645 - close: <string> 1646 - open: <string> 1647 - open24: <boolean> 1648 - tuesday: 1649 - close: <string> 1650 - open: <string> 1651 - open24: <boolean> 1652 - wednesday: 1653 - close: <string> 1654 - open: <string> 1655 - open24: <boolean> 1656 - name: <string> 1657 - phone: <string> 1658 - - departmentId: <string> 1659 - hours: 1660 - Open24: <boolean> 1661 - friday: 1662 - close: <string> 1663 - open: <string> 1664 - open24: <boolean> 1665 - monday: 1666 - close: <string> 1667 - open: <string> 1668 - open24: <boolean> 1669 - saturday: 1670 - close: <string> 1671 - open: <string> 1672 - open24: <boolean> 1673 - sunday: 1674 - close: <string> 1675 - open: <string> 1676 - open24: <boolean> 1677 - thursday: 1678 - close: <string> 1679 - open: <string> 1680 - open24: <boolean> 1681 - tuesday: 1682 - close: <string> 1683 - open: <string> 1684 - open24: <boolean> 1685 - wednesday: 1686 - close: <string> 1687 - open: <string> 1688 - open24: <boolean> 1689 - name: <string> 1690 - phone: <string> 1691 - divisionNumber: <string> 1692 - geolocation: 1693 - latLng: <string> 1694 - latitude: <number> 1695 - longitude: <number> 1696 - hours: 1697 - Open24: <boolean> 1698 - friday: 1699 - close: <string> 1700 - open: <string> 1701 - open24: <boolean> 1702 - gmtOffset: <string> 1703 - monday: 1704 - close: <string> 1705 - open: <string> 1706 - open24: <boolean> 1707 - saturday: 1708 - close: <string> 1709 - open: <string> 1710 - open24: <boolean> 1711 - sunday: 1712 - close: <string> 1713 - open: <string> 1714 - open24: <boolean> 1715 - thursday: 1716 - close: <string> 1717 - open: <string> 1718 - open24: <boolean> 1719 - timezone: <string> 1720 - tuesday: 1721 - close: <string> 1722 - open: <string> 1723 - open24: <boolean> 1724 - wednesday: 1725 - close: <string> 1726 - open: <string> 1727 - open24: <boolean> 1728 - locationId: <string> 1729 - name: <string> 1730 - phone: <string> 1731 - storeNumber: <string> 1732 - meta: 1733 - pagination: 1734 - limit: <number> 1735 - start: <number> 1736 - total: <number> 1737 - warnings: 1738 - - <string> 1739 - - <string> 1740 - '400': 1741 - description: Bad Request 1742 - content: 1743 - application/json: 1744 - schema: 1745 - type: object 1746 - properties: 1747 - errors: 1748 - type: object 1749 - properties: 1750 - code: 1751 - type: string 1752 - example: <string> 1753 - reason: 1754 - type: string 1755 - example: <string> 1756 - timestamp: 1757 - type: number 1758 - example: <number> 1759 - examples: 1760 - Bad Request: 1761 - value: 1762 - errors: 1763 - code: <string> 1764 - reason: <string> 1765 - timestamp: <number> 1766 - '401': 1767 - description: Unauthorized 1768 - content: 1769 - application/json: 1770 - schema: 1771 - type: object 1772 - properties: 1773 - errors: 1774 - type: object 1775 - properties: 1776 - error: 1777 - type: string 1778 - example: <string> 1779 - error_description: 1780 - type: string 1781 - example: <string> 1782 - examples: 1783 - Unauthorized: 1784 - value: 1785 - errors: 1786 - error: <string> 1787 - error_description: <string> 1788 - '404': 1789 - description: Not Found 1790 - content: 1791 - application/octet-stream: 1792 - examples: 1793 - Not Found: 1794 - value: '"<object>"' 1795 - '500': 1796 - description: Internal Server Error 1797 - content: 1798 - application/json: 1799 - schema: 1800 - type: object 1801 - properties: 1802 - errors: 1803 - type: object 1804 - properties: 1805 - code: 1806 - type: string 1807 - example: <string> 1808 - reason: 1809 - type: string 1810 - example: <string> 1811 - timestamp: 1812 - type: number 1813 - example: <number> 1814 - examples: 1815 - Internal Server Error: 1816 - value: 1817 - errors: 1818 - code: <string> 1819 - reason: <string> 1820 - timestamp: <number> 1821 - parameters: 1822 - - name: locationId 1823 - in: path 1824 - required: true 1825 - schema: 1826 - type: string 1827 - example: <string> 1828 - description: The locationId of the store you want results limited to. 1829 - /locations: 1830 - get: 1831 - security: 1832 - - OAuth2AuthorizationCode: [] 1833 - - OAuth2ClientCreds: [] 1834 - 1835 - tags: 1836 - - Locations 1837 - summary: Location list 1838 - description: >- 1839 - Provides access to a list of locations matching a given criteria. If the 1840 - parameter `filter.chain` is not provided, the results include all 1841 - locations and chains owned by The Kroger Co.<br> <h3>Starting Point 1842 - Required</h3> You must include one of the following parameters as a 1843 - starting point to narrow search results:<br><br> <ul> <li> 1844 - <code>filter.zipCode.near</code></li> <li> 1845 - <code>filter.latLong.near</code></li> <li> <code>filter.lat.near</code> 1846 - and <code>filter.lon.near</code></li> </ul><br> If you do not provide a 1847 - starting point or provide more than one starting point, an error warning 1848 - is returned. By default, the results are limited to 10 locations within 1849 - a 10-mile radius of the provided starting point. If you would like to 1850 - extend the search results, you can use the parameter 1851 - `filter.radiusInMiles` to set a new mile radius or `filter.limit` to set 1852 - the number of results returned. 1853 - operationId: locationList 1854 - parameters: 1855 - - name: filter.zipCode.near 1856 - in: query 1857 - schema: 1858 - type: string 1859 - example: <string> 1860 - description: The zip code you want to use as a starting point for results. 1861 - - name: filter.latLong.near 1862 - in: query 1863 - schema: 1864 - type: string 1865 - example: <string> 1866 - description: >- 1867 - The latitude and longitude you want to use as a starting point for 1868 - results. 1869 - - name: filter.lat.near 1870 - in: query 1871 - schema: 1872 - type: string 1873 - example: <string> 1874 - description: The latitude you want to use as a starting point for results. 1875 - - name: filter.lon.near 1876 - in: query 1877 - schema: 1878 - type: string 1879 - example: <string> 1880 - description: The longitude you want to use as a starting point for results. 1881 - - name: filter.radiusInMiles 1882 - in: query 1883 - schema: 1884 - type: string 1885 - example: '10' 1886 - description: The mile radius you want results limited to. 1887 - - name: filter.limit 1888 - in: query 1889 - schema: 1890 - type: string 1891 - example: '10' 1892 - description: The number of results you want returned. 1893 - - name: filter.chain 1894 - in: query 1895 - schema: 1896 - type: string 1897 - example: <string> 1898 - description: >- 1899 - The chain name of the chain you want results limited to. When using 1900 - this filter, only stores matching the provided chain name are 1901 - returned. 1902 - - name: filter.department 1903 - in: query 1904 - schema: 1905 - type: string 1906 - example: <string> 1907 - description: >- 1908 - The departmentId of the department you want results limited to. 1909 - Lists must be comma-separated. When using this filter, only stores 1910 - who have all of the departments provided are returned. 1911 - responses: 1912 - '200': 1913 - description: OK 1914 - content: 1915 - application/json: 1916 - schema: 1917 - type: object 1918 - properties: 1919 - data: 1920 - type: array 1921 - items: 1922 - type: object 1923 - properties: 1924 - address: 1925 - type: object 1926 - properties: 1927 - addressLine1: 1928 - type: string 1929 - example: <string> 1930 - addressLine2: 1931 - type: string 1932 - example: <string> 1933 - city: 1934 - type: string 1935 - example: <string> 1936 - county: 1937 - type: string 1938 - example: <string> 1939 - state: 1940 - type: string 1941 - example: <string> 1942 - zipCode: 1943 - type: string 1944 - example: <string> 1945 - chain: 1946 - type: string 1947 - example: <string> 1948 - departments: 1949 - type: array 1950 - items: 1951 - type: object 1952 - properties: 1953 - departmentId: 1954 - type: string 1955 - example: <string> 1956 - hours: 1957 - type: object 1958 - properties: 1959 - Open24: 1960 - type: boolean 1961 - example: <boolean> 1962 - friday: 1963 - type: object 1964 - properties: 1965 - close: 1966 - type: string 1967 - example: <string> 1968 - open: 1969 - type: string 1970 - example: <string> 1971 - open24: 1972 - type: boolean 1973 - example: <boolean> 1974 - monday: 1975 - type: object 1976 - properties: 1977 - close: 1978 - type: string 1979 - example: <string> 1980 - open: 1981 - type: string 1982 - example: <string> 1983 - open24: 1984 - type: boolean 1985 - example: <boolean> 1986 - saturday: 1987 - type: object 1988 - properties: 1989 - close: 1990 - type: string 1991 - example: <string> 1992 - open: 1993 - type: string 1994 - example: <string> 1995 - open24: 1996 - type: boolean 1997 - example: <boolean> 1998 - sunday: 1999 - type: object 2000 - properties: 2001 - close: 2002 - type: string 2003 - example: <string> 2004 - open: 2005 - type: string 2006 - example: <string> 2007 - open24: 2008 - type: boolean 2009 - example: <boolean> 2010 - thursday: 2011 - type: object 2012 - properties: 2013 - close: 2014 - type: string 2015 - example: <string> 2016 - open: 2017 - type: string 2018 - example: <string> 2019 - open24: 2020 - type: boolean 2021 - example: <boolean> 2022 - tuesday: 2023 - type: object 2024 - properties: 2025 - close: 2026 - type: string 2027 - example: <string> 2028 - open: 2029 - type: string 2030 - example: <string> 2031 - open24: 2032 - type: boolean 2033 - example: <boolean> 2034 - wednesday: 2035 - type: object 2036 - properties: 2037 - close: 2038 - type: string 2039 - example: <string> 2040 - open: 2041 - type: string 2042 - example: <string> 2043 - open24: 2044 - type: boolean 2045 - example: <boolean> 2046 - name: 2047 - type: string 2048 - example: <string> 2049 - phone: 2050 - type: string 2051 - example: <string> 2052 - example: 2053 - - departmentId: <string> 2054 - hours: 2055 - Open24: <boolean> 2056 - friday: 2057 - close: <string> 2058 - open: <string> 2059 - open24: <boolean> 2060 - monday: 2061 - close: <string> 2062 - open: <string> 2063 - open24: <boolean> 2064 - saturday: 2065 - close: <string> 2066 - open: <string> 2067 - open24: <boolean> 2068 - sunday: 2069 - close: <string> 2070 - open: <string> 2071 - open24: <boolean> 2072 - thursday: 2073 - close: <string> 2074 - open: <string> 2075 - open24: <boolean> 2076 - tuesday: 2077 - close: <string> 2078 - open: <string> 2079 - open24: <boolean> 2080 - wednesday: 2081 - close: <string> 2082 - open: <string> 2083 - open24: <boolean> 2084 - name: <string> 2085 - phone: <string> 2086 - - departmentId: <string> 2087 - hours: 2088 - Open24: <boolean> 2089 - friday: 2090 - close: <string> 2091 - open: <string> 2092 - open24: <boolean> 2093 - monday: 2094 - close: <string> 2095 - open: <string> 2096 - open24: <boolean> 2097 - saturday: 2098 - close: <string> 2099 - open: <string> 2100 - open24: <boolean> 2101 - sunday: 2102 - close: <string> 2103 - open: <string> 2104 - open24: <boolean> 2105 - thursday: 2106 - close: <string> 2107 - open: <string> 2108 - open24: <boolean> 2109 - tuesday: 2110 - close: <string> 2111 - open: <string> 2112 - open24: <boolean> 2113 - wednesday: 2114 - close: <string> 2115 - open: <string> 2116 - open24: <boolean> 2117 - name: <string> 2118 - phone: <string> 2119 - divisionNumber: 2120 - type: string 2121 - example: <string> 2122 - geolocation: 2123 - type: object 2124 - properties: 2125 - latLng: 2126 - type: string 2127 - example: <string> 2128 - latitude: 2129 - type: number 2130 - example: <number> 2131 - longitude: 2132 - type: number 2133 - example: <number> 2134 - hours: 2135 - type: object 2136 - properties: 2137 - Open24: 2138 - type: boolean 2139 - example: <boolean> 2140 - friday: 2141 - type: object 2142 - properties: 2143 - close: 2144 - type: string 2145 - example: <string> 2146 - open: 2147 - type: string 2148 - example: <string> 2149 - open24: 2150 - type: boolean 2151 - example: <boolean> 2152 - gmtOffset: 2153 - type: string 2154 - example: <string> 2155 - monday: 2156 - type: object 2157 - properties: 2158 - close: 2159 - type: string 2160 - example: <string> 2161 - open: 2162 - type: string 2163 - example: <string> 2164 - open24: 2165 - type: boolean 2166 - example: <boolean> 2167 - saturday: 2168 - type: object 2169 - properties: 2170 - close: 2171 - type: string 2172 - example: <string> 2173 - open: 2174 - type: string 2175 - example: <string> 2176 - open24: 2177 - type: boolean 2178 - example: <boolean> 2179 - sunday: 2180 - type: object 2181 - properties: 2182 - close: 2183 - type: string 2184 - example: <string> 2185 - open: 2186 - type: string 2187 - example: <string> 2188 - open24: 2189 - type: boolean 2190 - example: <boolean> 2191 - thursday: 2192 - type: object 2193 - properties: 2194 - close: 2195 - type: string 2196 - example: <string> 2197 - open: 2198 - type: string 2199 - example: <string> 2200 - open24: 2201 - type: boolean 2202 - example: <boolean> 2203 - timezone: 2204 - type: string 2205 - example: <string> 2206 - tuesday: 2207 - type: object 2208 - properties: 2209 - close: 2210 - type: string 2211 - example: <string> 2212 - open: 2213 - type: string 2214 - example: <string> 2215 - open24: 2216 - type: boolean 2217 - example: <boolean> 2218 - wednesday: 2219 - type: object 2220 - properties: 2221 - close: 2222 - type: string 2223 - example: <string> 2224 - open: 2225 - type: string 2226 - example: <string> 2227 - open24: 2228 - type: boolean 2229 - example: <boolean> 2230 - locationId: 2231 - type: string 2232 - example: <string> 2233 - name: 2234 - type: string 2235 - example: <string> 2236 - phone: 2237 - type: string 2238 - example: <string> 2239 - storeNumber: 2240 - type: string 2241 - example: <string> 2242 - example: 2243 - - address: 2244 - addressLine1: <string> 2245 - addressLine2: <string> 2246 - city: <string> 2247 - county: <string> 2248 - state: <string> 2249 - zipCode: <string> 2250 - chain: <string> 2251 - departments: 2252 - - departmentId: <string> 2253 - hours: 2254 - Open24: <boolean> 2255 - friday: 2256 - close: <string> 2257 - open: <string> 2258 - open24: <boolean> 2259 - monday: 2260 - close: <string> 2261 - open: <string> 2262 - open24: <boolean> 2263 - saturday: 2264 - close: <string> 2265 - open: <string> 2266 - open24: <boolean> 2267 - sunday: 2268 - close: <string> 2269 - open: <string> 2270 - open24: <boolean> 2271 - thursday: 2272 - close: <string> 2273 - open: <string> 2274 - open24: <boolean> 2275 - tuesday: 2276 - close: <string> 2277 - open: <string> 2278 - open24: <boolean> 2279 - wednesday: 2280 - close: <string> 2281 - open: <string> 2282 - open24: <boolean> 2283 - name: <string> 2284 - phone: <string> 2285 - - departmentId: <string> 2286 - hours: 2287 - Open24: <boolean> 2288 - friday: 2289 - close: <string> 2290 - open: <string> 2291 - open24: <boolean> 2292 - monday: 2293 - close: <string> 2294 - open: <string> 2295 - open24: <boolean> 2296 - saturday: 2297 - close: <string> 2298 - open: <string> 2299 - open24: <boolean> 2300 - sunday: 2301 - close: <string> 2302 - open: <string> 2303 - open24: <boolean> 2304 - thursday: 2305 - close: <string> 2306 - open: <string> 2307 - open24: <boolean> 2308 - tuesday: 2309 - close: <string> 2310 - open: <string> 2311 - open24: <boolean> 2312 - wednesday: 2313 - close: <string> 2314 - open: <string> 2315 - open24: <boolean> 2316 - name: <string> 2317 - phone: <string> 2318 - divisionNumber: <string> 2319 - geolocation: 2320 - latLng: <string> 2321 - latitude: <number> 2322 - longitude: <number> 2323 - hours: 2324 - Open24: <boolean> 2325 - friday: 2326 - close: <string> 2327 - open: <string> 2328 - open24: <boolean> 2329 - gmtOffset: <string> 2330 - monday: 2331 - close: <string> 2332 - open: <string> 2333 - open24: <boolean> 2334 - saturday: 2335 - close: <string> 2336 - open: <string> 2337 - open24: <boolean> 2338 - sunday: 2339 - close: <string> 2340 - open: <string> 2341 - open24: <boolean> 2342 - thursday: 2343 - close: <string> 2344 - open: <string> 2345 - open24: <boolean> 2346 - timezone: <string> 2347 - tuesday: 2348 - close: <string> 2349 - open: <string> 2350 - open24: <boolean> 2351 - wednesday: 2352 - close: <string> 2353 - open: <string> 2354 - open24: <boolean> 2355 - locationId: <string> 2356 - name: <string> 2357 - phone: <string> 2358 - storeNumber: <string> 2359 - - address: 2360 - addressLine1: <string> 2361 - addressLine2: <string> 2362 - city: <string> 2363 - county: <string> 2364 - state: <string> 2365 - zipCode: <string> 2366 - chain: <string> 2367 - departments: 2368 - - departmentId: <string> 2369 - hours: 2370 - Open24: <boolean> 2371 - friday: 2372 - close: <string> 2373 - open: <string> 2374 - open24: <boolean> 2375 - monday: 2376 - close: <string> 2377 - open: <string> 2378 - open24: <boolean> 2379 - saturday: 2380 - close: <string> 2381 - open: <string> 2382 - open24: <boolean> 2383 - sunday: 2384 - close: <string> 2385 - open: <string> 2386 - open24: <boolean> 2387 - thursday: 2388 - close: <string> 2389 - open: <string> 2390 - open24: <boolean> 2391 - tuesday: 2392 - close: <string> 2393 - open: <string> 2394 - open24: <boolean> 2395 - wednesday: 2396 - close: <string> 2397 - open: <string> 2398 - open24: <boolean> 2399 - name: <string> 2400 - phone: <string> 2401 - - departmentId: <string> 2402 - hours: 2403 - Open24: <boolean> 2404 - friday: 2405 - close: <string> 2406 - open: <string> 2407 - open24: <boolean> 2408 - monday: 2409 - close: <string> 2410 - open: <string> 2411 - open24: <boolean> 2412 - saturday: 2413 - close: <string> 2414 - open: <string> 2415 - open24: <boolean> 2416 - sunday: 2417 - close: <string> 2418 - open: <string> 2419 - open24: <boolean> 2420 - thursday: 2421 - close: <string> 2422 - open: <string> 2423 - open24: <boolean> 2424 - tuesday: 2425 - close: <string> 2426 - open: <string> 2427 - open24: <boolean> 2428 - wednesday: 2429 - close: <string> 2430 - open: <string> 2431 - open24: <boolean> 2432 - name: <string> 2433 - phone: <string> 2434 - divisionNumber: <string> 2435 - geolocation: 2436 - latLng: <string> 2437 - latitude: <number> 2438 - longitude: <number> 2439 - hours: 2440 - Open24: <boolean> 2441 - friday: 2442 - close: <string> 2443 - open: <string> 2444 - open24: <boolean> 2445 - gmtOffset: <string> 2446 - monday: 2447 - close: <string> 2448 - open: <string> 2449 - open24: <boolean> 2450 - saturday: 2451 - close: <string> 2452 - open: <string> 2453 - open24: <boolean> 2454 - sunday: 2455 - close: <string> 2456 - open: <string> 2457 - open24: <boolean> 2458 - thursday: 2459 - close: <string> 2460 - open: <string> 2461 - open24: <boolean> 2462 - timezone: <string> 2463 - tuesday: 2464 - close: <string> 2465 - open: <string> 2466 - open24: <boolean> 2467 - wednesday: 2468 - close: <string> 2469 - open: <string> 2470 - open24: <boolean> 2471 - locationId: <string> 2472 - name: <string> 2473 - phone: <string> 2474 - storeNumber: <string> 2475 - meta: 2476 - type: object 2477 - properties: 2478 - pagination: 2479 - type: object 2480 - properties: 2481 - limit: 2482 - type: number 2483 - example: <number> 2484 - start: 2485 - type: number 2486 - example: <number> 2487 - total: 2488 - type: number 2489 - example: <number> 2490 - warnings: 2491 - type: array 2492 - items: 2493 - type: string 2494 - example: <string> 2495 - example: 2496 - - <string> 2497 - - <string> 2498 - examples: 2499 - OK: 2500 - value: 2501 - data: 2502 - - address: 2503 - addressLine1: <string> 2504 - addressLine2: <string> 2505 - city: <string> 2506 - county: <string> 2507 - state: <string> 2508 - zipCode: <string> 2509 - chain: <string> 2510 - departments: 2511 - - departmentId: <string> 2512 - hours: 2513 - Open24: <boolean> 2514 - friday: 2515 - close: <string> 2516 - open: <string> 2517 - open24: <boolean> 2518 - monday: 2519 - close: <string> 2520 - open: <string> 2521 - open24: <boolean> 2522 - saturday: 2523 - close: <string> 2524 - open: <string> 2525 - open24: <boolean> 2526 - sunday: 2527 - close: <string> 2528 - open: <string> 2529 - open24: <boolean> 2530 - thursday: 2531 - close: <string> 2532 - open: <string> 2533 - open24: <boolean> 2534 - tuesday: 2535 - close: <string> 2536 - open: <string> 2537 - open24: <boolean> 2538 - wednesday: 2539 - close: <string> 2540 - open: <string> 2541 - open24: <boolean> 2542 - name: <string> 2543 - phone: <string> 2544 - - departmentId: <string> 2545 - hours: 2546 - Open24: <boolean> 2547 - friday: 2548 - close: <string> 2549 - open: <string> 2550 - open24: <boolean> 2551 - monday: 2552 - close: <string> 2553 - open: <string> 2554 - open24: <boolean> 2555 - saturday: 2556 - close: <string> 2557 - open: <string> 2558 - open24: <boolean> 2559 - sunday: 2560 - close: <string> 2561 - open: <string> 2562 - open24: <boolean> 2563 - thursday: 2564 - close: <string> 2565 - open: <string> 2566 - open24: <boolean> 2567 - tuesday: 2568 - close: <string> 2569 - open: <string> 2570 - open24: <boolean> 2571 - wednesday: 2572 - close: <string> 2573 - open: <string> 2574 - open24: <boolean> 2575 - name: <string> 2576 - phone: <string> 2577 - divisionNumber: <string> 2578 - geolocation: 2579 - latLng: <string> 2580 - latitude: <number> 2581 - longitude: <number> 2582 - hours: 2583 - Open24: <boolean> 2584 - friday: 2585 - close: <string> 2586 - open: <string> 2587 - open24: <boolean> 2588 - gmtOffset: <string> 2589 - monday: 2590 - close: <string> 2591 - open: <string> 2592 - open24: <boolean> 2593 - saturday: 2594 - close: <string> 2595 - open: <string> 2596 - open24: <boolean> 2597 - sunday: 2598 - close: <string> 2599 - open: <string> 2600 - open24: <boolean> 2601 - thursday: 2602 - close: <string> 2603 - open: <string> 2604 - open24: <boolean> 2605 - timezone: <string> 2606 - tuesday: 2607 - close: <string> 2608 - open: <string> 2609 - open24: <boolean> 2610 - wednesday: 2611 - close: <string> 2612 - open: <string> 2613 - open24: <boolean> 2614 - locationId: <string> 2615 - name: <string> 2616 - phone: <string> 2617 - storeNumber: <string> 2618 - - address: 2619 - addressLine1: <string> 2620 - addressLine2: <string> 2621 - city: <string> 2622 - county: <string> 2623 - state: <string> 2624 - zipCode: <string> 2625 - chain: <string> 2626 - departments: 2627 - - departmentId: <string> 2628 - hours: 2629 - Open24: <boolean> 2630 - friday: 2631 - close: <string> 2632 - open: <string> 2633 - open24: <boolean> 2634 - monday: 2635 - close: <string> 2636 - open: <string> 2637 - open24: <boolean> 2638 - saturday: 2639 - close: <string> 2640 - open: <string> 2641 - open24: <boolean> 2642 - sunday: 2643 - close: <string> 2644 - open: <string> 2645 - open24: <boolean> 2646 - thursday: 2647 - close: <string> 2648 - open: <string> 2649 - open24: <boolean> 2650 - tuesday: 2651 - close: <string> 2652 - open: <string> 2653 - open24: <boolean> 2654 - wednesday: 2655 - close: <string> 2656 - open: <string> 2657 - open24: <boolean> 2658 - name: <string> 2659 - phone: <string> 2660 - - departmentId: <string> 2661 - hours: 2662 - Open24: <boolean> 2663 - friday: 2664 - close: <string> 2665 - open: <string> 2666 - open24: <boolean> 2667 - monday: 2668 - close: <string> 2669 - open: <string> 2670 - open24: <boolean> 2671 - saturday: 2672 - close: <string> 2673 - open: <string> 2674 - open24: <boolean> 2675 - sunday: 2676 - close: <string> 2677 - open: <string> 2678 - open24: <boolean> 2679 - thursday: 2680 - close: <string> 2681 - open: <string> 2682 - open24: <boolean> 2683 - tuesday: 2684 - close: <string> 2685 - open: <string> 2686 - open24: <boolean> 2687 - wednesday: 2688 - close: <string> 2689 - open: <string> 2690 - open24: <boolean> 2691 - name: <string> 2692 - phone: <string> 2693 - divisionNumber: <string> 2694 - geolocation: 2695 - latLng: <string> 2696 - latitude: <number> 2697 - longitude: <number> 2698 - hours: 2699 - Open24: <boolean> 2700 - friday: 2701 - close: <string> 2702 - open: <string> 2703 - open24: <boolean> 2704 - gmtOffset: <string> 2705 - monday: 2706 - close: <string> 2707 - open: <string> 2708 - open24: <boolean> 2709 - saturday: 2710 - close: <string> 2711 - open: <string> 2712 - open24: <boolean> 2713 - sunday: 2714 - close: <string> 2715 - open: <string> 2716 - open24: <boolean> 2717 - thursday: 2718 - close: <string> 2719 - open: <string> 2720 - open24: <boolean> 2721 - timezone: <string> 2722 - tuesday: 2723 - close: <string> 2724 - open: <string> 2725 - open24: <boolean> 2726 - wednesday: 2727 - close: <string> 2728 - open: <string> 2729 - open24: <boolean> 2730 - locationId: <string> 2731 - name: <string> 2732 - phone: <string> 2733 - storeNumber: <string> 2734 - meta: 2735 - pagination: 2736 - limit: <number> 2737 - start: <number> 2738 - total: <number> 2739 - warnings: 2740 - - <string> 2741 - - <string> 2742 - '400': 2743 - description: Bad Request 2744 - content: 2745 - application/json: 2746 - schema: 2747 - type: object 2748 - properties: 2749 - errors: 2750 - type: string 2751 - example: <object> 2752 - examples: 2753 - Bad Request: 2754 - value: 2755 - errors: <object> 2756 - '401': 2757 - description: Unauthorized 2758 - content: 2759 - application/json: 2760 - schema: 2761 - type: object 2762 - properties: 2763 - errors: 2764 - type: object 2765 - properties: 2766 - error: 2767 - type: string 2768 - example: <string> 2769 - error_description: 2770 - type: string 2771 - example: <string> 2772 - examples: 2773 - Unauthorized: 2774 - value: 2775 - errors: 2776 - error: <string> 2777 - error_description: <string> 2778 - '500': 2779 - description: Internal Server Error 2780 - content: 2781 - application/json: 2782 - schema: 2783 - type: object 2784 - properties: 2785 - errors: 2786 - type: object 2787 - properties: 2788 - code: 2789 - type: string 2790 - example: <string> 2791 - reason: 2792 - type: string 2793 - example: <string> 2794 - timestamp: 2795 - type: number 2796 - example: <number> 2797 - examples: 2798 - Internal Server Error: 2799 - value: 2800 - errors: 2801 - code: <string> 2802 - reason: <string> 2803 - timestamp: <number> 2804 - /chains: 2805 - get: 2806 - security: 2807 - - OAuth2AuthorizationCode: [] 2808 - - OAuth2ClientCreds: [] 2809 - 2810 - tags: 2811 - - Locations 2812 - - Chains 2813 - summary: Chain list 2814 - description: Provides access to a list of all chains owned by The Kroger Co. 2815 - operationId: chainList 2816 - responses: 2817 - '200': 2818 - description: OK 2819 - content: 2820 - application/json: 2821 - schema: 2822 - type: object 2823 - properties: 2824 - data: 2825 - type: array 2826 - items: 2827 - type: object 2828 - properties: 2829 - divisionNumbers: 2830 - type: array 2831 - items: 2832 - type: string 2833 - example: <string> 2834 - example: 2835 - - <string> 2836 - - <string> 2837 - name: 2838 - type: string 2839 - example: <string> 2840 - example: 2841 - - divisionNumbers: 2842 - - <string> 2843 - - <string> 2844 - name: <string> 2845 - - divisionNumbers: 2846 - - <string> 2847 - - <string> 2848 - name: <string> 2849 - meta: 2850 - type: object 2851 - properties: 2852 - pagination: 2853 - type: object 2854 - properties: 2855 - limit: 2856 - type: number 2857 - example: <number> 2858 - start: 2859 - type: number 2860 - example: <number> 2861 - total: 2862 - type: number 2863 - example: <number> 2864 - warnings: 2865 - type: array 2866 - items: 2867 - type: string 2868 - example: <string> 2869 - example: 2870 - - <string> 2871 - - <string> 2872 - examples: 2873 - OK: 2874 - value: 2875 - data: 2876 - - divisionNumbers: 2877 - - <string> 2878 - - <string> 2879 - name: <string> 2880 - - divisionNumbers: 2881 - - <string> 2882 - - <string> 2883 - name: <string> 2884 - meta: 2885 - pagination: 2886 - limit: <number> 2887 - start: <number> 2888 - total: <number> 2889 - warnings: 2890 - - <string> 2891 - - <string> 2892 - '400': 2893 - description: Bad Request 2894 - content: 2895 - application/json: 2896 - schema: 2897 - type: object 2898 - properties: 2899 - errors: 2900 - type: string 2901 - example: <object> 2902 - examples: 2903 - Bad Request: 2904 - value: 2905 - errors: <object> 2906 - '401': 2907 - description: Unauthorized 2908 - content: 2909 - application/json: 2910 - schema: 2911 - type: object 2912 - properties: 2913 - errors: 2914 - type: object 2915 - properties: 2916 - error: 2917 - type: string 2918 - example: <string> 2919 - error_description: 2920 - type: string 2921 - example: <string> 2922 - examples: 2923 - Unauthorized: 2924 - value: 2925 - errors: 2926 - error: <string> 2927 - error_description: <string> 2928 - '500': 2929 - description: Internal Server Error 2930 - content: 2931 - application/json: 2932 - schema: 2933 - type: object 2934 - properties: 2935 - errors: 2936 - type: object 2937 - properties: 2938 - code: 2939 - type: string 2940 - example: <string> 2941 - reason: 2942 - type: string 2943 - example: <string> 2944 - timestamp: 2945 - type: number 2946 - example: <number> 2947 - examples: 2948 - Internal Server Error: 2949 - value: 2950 - errors: 2951 - code: <string> 2952 - reason: <string> 2953 - timestamp: <number> 2954 - /chains/{name}: 2955 - get: 2956 - security: 2957 - - OAuth2AuthorizationCode: [] 2958 - - OAuth2ClientCreds: [] 2959 - 2960 - tags: 2961 - - Locations 2962 - - Chains 2963 - summary: Chain details 2964 - description: >- 2965 - Provides access to the details of a specific chian by using the chain 2966 - `name`. 2967 - operationId: chainDetails 2968 - responses: 2969 - '200': 2970 - description: OK 2971 - content: 2972 - application/json: 2973 - schema: 2974 - type: object 2975 - properties: 2976 - data: 2977 - type: object 2978 - properties: 2979 - divisionNumbers: 2980 - type: array 2981 - items: 2982 - type: string 2983 - example: <string> 2984 - example: 2985 - - <string> 2986 - - <string> 2987 - name: 2988 - type: string 2989 - example: <string> 2990 - meta: 2991 - type: object 2992 - properties: {} 2993 - examples: 2994 - OK: 2995 - value: 2996 - data: 2997 - divisionNumbers: 2998 - - <string> 2999 - - <string> 3000 - name: <string> 3001 - meta: {} 3002 - '401': 3003 - description: Unauthorized 3004 - content: 3005 - application/json: 3006 - schema: 3007 - type: object 3008 - properties: 3009 - errors: 3010 - type: object 3011 - properties: 3012 - error: 3013 - type: string 3014 - example: <string> 3015 - error_description: 3016 - type: string 3017 - example: <string> 3018 - examples: 3019 - Unauthorized: 3020 - value: 3021 - errors: 3022 - error: <string> 3023 - error_description: <string> 3024 - '404': 3025 - description: Not Found 3026 - content: 3027 - application/octet-stream: 3028 - examples: 3029 - Not Found: 3030 - value: '"<object>"' 3031 - '500': 3032 - description: Internal Server Error 3033 - content: 3034 - application/json: 3035 - schema: 3036 - type: object 3037 - properties: 3038 - errors: 3039 - type: object 3040 - properties: 3041 - code: 3042 - type: string 3043 - example: <string> 3044 - reason: 3045 - type: string 3046 - example: <string> 3047 - timestamp: 3048 - type: number 3049 - example: <number> 3050 - examples: 3051 - Internal Server Error: 3052 - value: 3053 - errors: 3054 - code: <string> 3055 - reason: <string> 3056 - timestamp: <number> 3057 - parameters: 3058 - - name: name 3059 - in: path 3060 - required: true 3061 - schema: 3062 - type: string 3063 - example: <string> 3064 - description: >- 3065 - The name of a chain owned by The Kroger Co. Note the chain "name" is 3066 - returned from the /chains endpoint as "name" and from the /locations 3067 - endpoint as "chain". 3068 - /departments: 3069 - get: 3070 - security: 3071 - - OAuth2AuthorizationCode: [] 3072 - - OAuth2ClientCreds: [] 3073 - tags: 3074 - - Locations 3075 - - Departments 3076 - summary: Department list 3077 - description: >- 3078 - Provides access to a list of all departments, including departments of 3079 - chains owned by The Kroger Co. 3080 - operationId: departmentList 3081 - responses: 3082 - '200': 3083 - description: OK 3084 - content: 3085 - application/json: 3086 - schema: 3087 - type: object 3088 - properties: 3089 - data: 3090 - type: array 3091 - items: 3092 - type: object 3093 - properties: 3094 - departmentId: 3095 - type: string 3096 - example: <string> 3097 - name: 3098 - type: string 3099 - example: <string> 3100 - example: 3101 - - departmentId: <string> 3102 - name: <string> 3103 - - departmentId: <string> 3104 - name: <string> 3105 - meta: 3106 - type: object 3107 - properties: 3108 - pagination: 3109 - type: object 3110 - properties: 3111 - limit: 3112 - type: number 3113 - example: <number> 3114 - start: 3115 - type: number 3116 - example: <number> 3117 - total: 3118 - type: number 3119 - example: <number> 3120 - warnings: 3121 - type: array 3122 - items: 3123 - type: string 3124 - example: <string> 3125 - example: 3126 - - <string> 3127 - - <string> 3128 - examples: 3129 - OK: 3130 - value: 3131 - data: 3132 - - departmentId: <string> 3133 - name: <string> 3134 - - departmentId: <string> 3135 - name: <string> 3136 - meta: 3137 - pagination: 3138 - limit: <number> 3139 - start: <number> 3140 - total: <number> 3141 - warnings: 3142 - - <string> 3143 - - <string> 3144 - '401': 3145 - description: Unauthorized 3146 - content: 3147 - application/json: 3148 - schema: 3149 - type: object 3150 - properties: 3151 - errors: 3152 - type: object 3153 - properties: 3154 - error: 3155 - type: string 3156 - example: <string> 3157 - error_description: 3158 - type: string 3159 - example: <string> 3160 - examples: 3161 - Unauthorized: 3162 - value: 3163 - errors: 3164 - error: <string> 3165 - error_description: <string> 3166 - '500': 3167 - description: Internal Server Error 3168 - content: 3169 - application/json: 3170 - schema: 3171 - type: object 3172 - properties: 3173 - errors: 3174 - type: object 3175 - properties: 3176 - code: 3177 - type: string 3178 - example: <string> 3179 - reason: 3180 - type: string 3181 - example: <string> 3182 - timestamp: 3183 - type: number 3184 - example: <number> 3185 - examples: 3186 - Internal Server Error: 3187 - value: 3188 - errors: 3189 - code: <string> 3190 - reason: <string> 3191 - timestamp: <number> 3192 - /departments/{id}: 3193 - get: 3194 - tags: 3195 - - Locations 3196 - - Departments 3197 - summary: Department details 3198 - description: >- 3199 - Provides access to the details of a specific department by using the 3200 - `departmentId`. 3201 - operationId: departmentDetails 3202 - responses: 3203 - '200': 3204 - description: OK 3205 - content: 3206 - application/json: 3207 - schema: 3208 - type: object 3209 - properties: 3210 - data: 3211 - type: object 3212 - properties: 3213 - departmentId: 3214 - type: string 3215 - example: <string> 3216 - name: 3217 - type: string 3218 - example: <string> 3219 - meta: 3220 - type: object 3221 - properties: {} 3222 - examples: 3223 - OK: 3224 - value: 3225 - data: 3226 - departmentId: <string> 3227 - name: <string> 3228 - meta: {} 3229 - '400': 3230 - description: Bad Request 3231 - content: 3232 - application/json: 3233 - schema: 3234 - type: object 3235 - properties: 3236 - errors: 3237 - type: object 3238 - properties: 3239 - code: 3240 - type: string 3241 - example: <string> 3242 - reason: 3243 - type: string 3244 - example: <string> 3245 - timestamp: 3246 - type: number 3247 - example: <number> 3248 - examples: 3249 - Bad Request: 3250 - value: 3251 - errors: 3252 - code: <string> 3253 - reason: <string> 3254 - timestamp: <number> 3255 - '401': 3256 - description: Unauthorized 3257 - content: 3258 - application/json: 3259 - schema: 3260 - type: object 3261 - properties: 3262 - errors: 3263 - type: object 3264 - properties: 3265 - error: 3266 - type: string 3267 - example: <string> 3268 - error_description: 3269 - type: string 3270 - example: <string> 3271 - examples: 3272 - Unauthorized: 3273 - value: 3274 - errors: 3275 - error: <string> 3276 - error_description: <string> 3277 - '404': 3278 - description: Not Found 3279 - content: 3280 - application/octet-stream: 3281 - examples: 3282 - Not Found: 3283 - value: '"<object>"' 3284 - '500': 3285 - description: Internal Server Error 3286 - content: 3287 - application/json: 3288 - schema: 3289 - type: object 3290 - properties: 3291 - errors: 3292 - type: object 3293 - properties: 3294 - code: 3295 - type: string 3296 - example: <string> 3297 - reason: 3298 - type: string 3299 - example: <string> 3300 - timestamp: 3301 - type: number 3302 - example: <number> 3303 - examples: 3304 - Internal Server Error: 3305 - value: 3306 - errors: 3307 - code: <string> 3308 - reason: <string> 3309 - timestamp: <number> 3310 - parameters: 3311 - - name: id 3312 - in: path 3313 - required: true 3314 - schema: 3315 - type: string 3316 - example: <string> 3317 - description: The departmentId of the department you want returned. 3318 - tags: 3319 - - name: Identity 3320 - description: >- 3321 - The Identity API provides access to the profile ID of authenticated 3322 - customers. 3323 - 3324 - 3325 - Public Rate Limit: 5,000 per day 3326 - - name: Cart 3327 - description: >- 3328 - The Cart API provides you with access to add an item to an authenticated 3329 - customer's cart. 3330 - 3331 - 3332 - Public Rate Limit: 5,000 per day 3333 - - name: Products 3334 - description: |- 3335 - The Products API provides access to our entire product catalog. 3336 - 3337 - Public Rate Limit: 10,000 per day 3338 - - name: Locations 3339 - description: >- 3340 - The Locations API provides access to all locations, chains, and 3341 - departments that are owned by The Kroger Co. 3342 - 3343 - 3344 - Public Rate Limit: 1,600 a day per endpoint 3345 - - name: Chains 3346 - - name: Departments
+1 -1
internal/locations/interface_test.go
··· 6 6 ) 7 7 8 8 var ( 9 - _ locationBackend = (*kroger.ClientWithResponses)(nil) 9 + _ locationBackend = (*kroger.LocationBackend)(nil) 10 10 _ locationBackend = (*walmart.Client)(nil) 11 11 )
+6 -1
internal/locations/storage.go
··· 6 6 "errors" 7 7 "fmt" 8 8 "log/slog" 9 + "net/http" 9 10 "sort" 10 11 "time" 11 12 ··· 26 27 locationtypes "careme/internal/locations/types" 27 28 28 29 "github.com/samber/lo" 30 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 29 31 "golang.org/x/sync/errgroup" 30 32 ) 31 33 ··· 79 81 } 80 82 81 83 ctx := context.Background() 84 + httpClient := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} 82 85 backendfactories := []locationBackendFactory{ 83 - func(context.Context) (locationBackend, error) { return kroger.FromConfig(cfg) }, 86 + func(context.Context) (locationBackend, error) { 87 + return kroger.NewLocationBackendFromConfig(cfg, httpClient) 88 + }, 84 89 func(context.Context) (locationBackend, error) { return walmart.NewClient(cfg.Walmart) }, 85 90 func(ctx context.Context) (locationBackend, error) { 86 91 return aldi.NewLocationBackendFromConfig(ctx, cfg, centroids)
+5 -3
internal/mail/mail.go
··· 30 30 "github.com/sendgrid/rest" 31 31 "github.com/sendgrid/sendgrid-go" 32 32 "github.com/sendgrid/sendgrid-go/helpers/mail" 33 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 33 34 "go.opentelemetry.io/otel" 34 35 "go.opentelemetry.io/otel/attribute" 35 36 ) ··· 73 74 } 74 75 75 76 userStorage := users.NewStorage(cache) 76 - mc := critique.NewManager(cfg, cache) 77 - ig := ingredientgrading.NewManager(cfg, cache) 77 + aiHTTPClient := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} 78 + mc := critique.NewManager(cfg, cache, aiHTTPClient) 79 + ig := ingredientgrading.NewManager(cfg, cache, aiHTTPClient) 78 80 staples, err := recipes.NewCachedStaplesService(cfg, cache, ig) 79 81 if err != nil { 80 82 return nil, fmt.Errorf("failed to create staples service: %w", err) 81 83 } 82 84 ss := recipes.StatusStore(cache) 83 - aiClient := ai.NewClient(cfg.AI.APIKey, "TODOMODEL") 85 + aiClient := ai.NewClient(cfg.AI.APIKey, "TODOMODEL", aiHTTPClient) 84 86 generator, err := recipes.NewGenerator(aiClient, mc, staples, ss) 85 87 if err != nil { 86 88 return nil, fmt.Errorf("failed to create recipe generator: %w", err)
+9 -2
internal/recipes/critique/manager.go
··· 4 4 "context" 5 5 "fmt" 6 6 "log/slog" 7 + "net/http" 7 8 "strings" 8 9 "sync" 9 10 10 11 "careme/internal/ai" 11 12 "careme/internal/cache" 12 13 "careme/internal/config" 14 + 15 + "go.opentelemetry.io/otel" 13 16 ) 14 17 15 18 const MinimumRecipeScore = 8 ··· 58 61 wg sync.WaitGroup 59 62 } 60 63 61 - func NewManager(cfg *config.Config, c cache.ListCache) Manager { 64 + func NewManager(cfg *config.Config, c cache.ListCache, httpClient *http.Client) Manager { 62 65 if !cfg.Gemini.IsEnabled() { 63 66 return rubberstamp{} 64 67 } 65 - crit := ai.NewCritiquer(cfg.Gemini.APIKey, cfg.Gemini.CritiqueModel) 68 + crit := ai.NewCritiquer(cfg.Gemini.APIKey, cfg.Gemini.CritiqueModel, httpClient) 66 69 return &multiCritiquer{ 67 70 critiquer: newCachingCritiquer(crit, NewStore(c)), 68 71 } ··· 72 75 return mc.critiquer.Ready(ctx) 73 76 } 74 77 78 + var tracer = otel.Tracer("careme/internal/recipes/critiques") 79 + 75 80 func (mc *multiCritiquer) CritiqueRecipes(ctx context.Context, recipes []ai.Recipe) <-chan Result { 76 81 results := make(chan Result, len(recipes)) 77 82 mc.wg.Add(len(recipes)) ··· 80 85 for _, recipe := range recipes { 81 86 localWg.Go(func() { 82 87 defer mc.wg.Done() 88 + ctx, span := tracer.Start(ctx, "critques.recipe") 89 + defer span.End() 83 90 critique, err := mc.critiquer.CritiqueRecipe(ctx, recipe) 84 91 results <- Result{ 85 92 Recipe: &recipe,
+1 -1
internal/recipes/critique/multi_test.go
··· 45 45 func TestNewServiceReturnsRubberstampWithoutGemini(t *testing.T) { 46 46 t.Parallel() 47 47 48 - svc := NewManager(&config.Config{}, cache.NewFileCache(t.TempDir())) 48 + svc := NewManager(&config.Config{}, cache.NewFileCache(t.TempDir()), nil) 49 49 50 50 results := svc.CritiqueRecipes(t.Context(), []ai.Recipe{{Title: "Weeknight Pasta"}}) 51 51 result, ok := <-results
+21 -2
internal/recipes/generator.go
··· 16 16 17 17 "github.com/samber/lo" 18 18 "github.com/samber/lo/mutable" 19 + "go.opentelemetry.io/otel" 20 + "go.opentelemetry.io/otel/attribute" 19 21 ) 20 22 21 23 type aiClient interface { ··· 39 41 statusWriter statusWriter 40 42 } 41 43 44 + var tracer = otel.Tracer("careme/internal/recipes") 45 + 42 46 func NewGenerator(aiClient aiClient, critiquer critique.Service, staples staplesService, statuses statusWriter) (*generatorService, error) { 43 47 if aiClient == nil { 44 48 return nil, fmt.Errorf("ai client is required") ··· 58 62 } 59 63 60 64 func (g *generatorService) PickAWine(ctx context.Context, location string, recipe ai.Recipe, date time.Time) (*ai.WineSelection, error) { 65 + ctx, span := tracer.Start(ctx, "recipes.pickawine") 66 + defer span.End() 61 67 var styles []string 62 68 for _, style := range recipe.WineStyles { 63 69 style = strings.TrimSpace(style) ··· 102 108 // if we have a response id one of the three should be true? Or did they just not care and hit try again? 103 109 if p.ResponseID != "" && (p.Instructions != "" || len(p.Saved) > 0 || len(p.Dismissed) > 0) { 104 110 slog.InfoContext(ctx, "Regenerating recipes for location", "location", p.String(), "response_id", p.ResponseID) 111 + ctx, span := tracer.Start(ctx, "recipes.regenerate") 112 + defer span.End() 105 113 instructions := regenerateInstructions(p) 106 114 115 + // TODO give them some sort of status. 107 116 shoppingList, err := g.aiClient.Regenerate(ctx, instructions, p.ResponseID) 108 117 if err != nil { 109 118 return nil, fmt.Errorf("failed to regenerate recipes with AI: %w", err) ··· 124 133 return shoppingList, nil 125 134 } 126 135 136 + ctx, span := tracer.Start(ctx, "recipes.generate") 137 + defer span.End() 127 138 slog.InfoContext(ctx, "Generating recipes for location", "location", p.String()) 128 139 ingredients, err := g.staples.FetchStaples(ctx, p) 129 140 if err != nil { 130 141 return nil, fmt.Errorf("failed to get staples: %w", err) 131 142 } 132 - g.writeStatus(ctx, hash, fmt.Sprintf("Looking through %d ingredients", len(ingredients))) 143 + ogCount := len(ingredients) 133 144 ingredients = lo.Filter(ingredients, func(ing ai.InputIngredient, _ int) bool { 134 145 // TODO make configurable? 135 - return ing.Grade == nil || ing.Grade.Score > 5 146 + return ing.Grade == nil || ing.Grade.Score > 6 136 147 }) 148 + // having category would be interesing here. 149 + g.writeStatus(ctx, hash, fmt.Sprintf("Considering %d out of %d ingredients", len(ingredients), ogCount)) 150 + 137 151 mutable.Shuffle(ingredients) 138 152 139 153 instructions := []string{p.Directive, p.Instructions} 154 + 140 155 shoppingList, err := g.aiClient.GenerateRecipes(ctx, p.Location, ingredients, instructions, p.Date, p.LastRecipes) 141 156 if err != nil { 142 157 return nil, fmt.Errorf("failed to generate recipes with AI: %w", err) ··· 206 221 if g.critiquer == nil { 207 222 return shoppingList, nil 208 223 } 224 + ctx, span := tracer.Start(ctx, "recipes.critique") 225 + defer span.End() 226 + 209 227 g.writeStatus(ctx, hash, titles("Getting feeeback on these recipes:", shoppingList.Recipes)) 210 228 results := g.critiquer.CritiqueRecipes(ctx, shoppingList.Recipes) 211 229 good, garbage := critique.Split(ctx, results, critique.MinimumRecipeScore) ··· 215 233 if len(garbage) == 0 { 216 234 return shoppingList, nil 217 235 } 236 + span.SetAttributes(attribute.Bool("regenaftercrique", true)) 218 237 slog.InfoContext(ctx, "Regenerating recipes based on critique feedback:", "garbage_count", len(garbage), "good_count", len(good)) 219 238 garbageRecipes := lo.Map(garbage, func(r critique.Result, _ int) ai.Recipe { return *r.Recipe }) 220 239 g.writeStatus(ctx, hash, titles("Making adjustments to these recipes: ", garbageRecipes))
+2 -2
internal/recipes/generator_hash_test.go
··· 23 23 } 24 24 25 25 // make sure we're intentional about breaking hash 26 - if h1 != "JjKXkKjKKpE" { 26 + if h1 != "wrxx3dmHzBA" { 27 27 t.Fatalf("expected hash to be stable and equal to JjKXkKjKKpE, got %s", h1) 28 28 } 29 29 ··· 31 31 if !ok { 32 32 t.Fatal("expected current hash passhed to legacy") 33 33 } 34 - if legacyHash != "cmVjaXBlJjKXkKjKKpE=" { 34 + if legacyHash != "cmVjaXBlwrxx3dmHzBA=" { 35 35 t.Fatalf("expected legacy hash to be base64 of recipe hash with prefix, got %s", legacyHash) 36 36 } 37 37
+7 -7
internal/recipes/generator_test.go
··· 423 423 } 424 424 critiquer := &captureCritiqueService{} 425 425 g := &generatorService{ 426 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 426 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 427 427 aiClient: aiStub, 428 428 critiquer: critiquer, 429 429 statusWriter: noopstatuswriter{}, ··· 525 525 } 526 526 527 527 g := &generatorService{ 528 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 528 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 529 529 aiClient: aiStub, 530 530 critiquer: critiquer, 531 531 statusWriter: noopstatuswriter{}, ··· 612 612 }, 613 613 } 614 614 g := &generatorService{ 615 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 615 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 616 616 aiClient: aiStub, 617 617 critiquer: critiquer, 618 618 statusWriter: noopstatuswriter{}, ··· 647 647 }}, 648 648 } 649 649 g := &generatorService{ 650 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 650 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 651 651 aiClient: aiStub, 652 652 critiquer: &captureCritiqueService{}, 653 653 statusWriter: noopstatuswriter{}, ··· 686 686 687 687 statuses := &statusCounter{} 688 688 g := &generatorService{ 689 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 689 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 690 690 aiClient: &sequenceAIClient{generateResponses: []*ai.ShoppingList{{ResponseID: "resp-stable", Recipes: []ai.Recipe{steady}}}}, 691 691 critiquer: &captureCritiqueService{}, 692 692 statusWriter: statuses, ··· 890 890 }, 891 891 } 892 892 g := &generatorService{ 893 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 893 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 894 894 aiClient: aiStub, 895 895 critiquer: critiquer, 896 896 statusWriter: noopstatuswriter{}, ··· 945 945 }, 946 946 } 947 947 g := &generatorService{ 948 - staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil)}, 948 + staples: &cachedStaplesService{cache: io, grader: ingredientgrading.NewManager(nil, nil, nil)}, 949 949 aiClient: aiStub, 950 950 critiquer: critiquer, 951 951 statusWriter: noopstatuswriter{},
-1
internal/recipes/io.go
··· 108 108 } 109 109 }() 110 110 111 - // this should be back compat with kroger.Ingredient 112 111 var ingredients []ai.InputIngredient 113 112 if err := json.NewDecoder(ingredientBlob).Decode(&ingredients); err != nil { 114 113 return nil, err
-4
internal/recipes/io_test.go
··· 267 267 t.Fatalf("unexpected cached wine recommendation: got %q", got) 268 268 } 269 269 } 270 - 271 - func loPtr(v string) *string { 272 - return &v 273 - }
+2 -6
internal/recipes/server_test.go
··· 632 632 } 633 633 634 634 captured := generator.LastParams() 635 - if captured == nil { 636 - t.Fatal("expected captured params") 637 - } 635 + require.NotNil(t, captured) 638 636 if got, want := captured.LastRecipes, []string{"Cooked Recently"}; !slices.Equal(got, want) { 639 637 t.Fatalf("expected only recently cooked recipes in avoid list, got %v", got) 640 638 } ··· 1610 1608 } 1611 1609 1612 1610 captured := generator.LastParams() 1613 - if captured == nil { 1614 - t.Fatal("expected captured params") 1615 - } 1611 + require.NotNil(t, captured) 1616 1612 if got, want := captured.PriorSavedHashes, []string{alreadySaved.ComputeHash()}; !slices.Equal(got, want) { 1617 1613 t.Fatalf("expected prior saved hashes %v, got %v", want, got) 1618 1614 }
+60 -102
internal/recipes/staples.go
··· 8 8 "hash/fnv" 9 9 "io" 10 10 "log/slog" 11 + "net/http" 11 12 "strings" 12 13 "testing" 13 14 "time" ··· 23 24 "careme/internal/wholefoods" 24 25 25 26 "github.com/samber/lo" 27 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 28 + "go.opentelemetry.io/otel/attribute" 26 29 ) 27 - 28 - // todo make this a indepenedent ingredient object not kroger. 29 - // we're cheating and making the wrapper here do the conversion for now but all underlying provider should create input ingredients 30 - // then this becomes staplesProvider 31 - type krogerProvider interface { 32 - FetchStaples(ctx context.Context, locationID string) ([]kroger.Ingredient, error) 33 - GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]kroger.Ingredient, error) 34 - } 35 30 36 31 type identityProvider interface { 37 32 IsID(locationID string) bool ··· 40 35 41 36 type backendStaplesProvider interface { 42 37 identityProvider 43 - krogerProvider 38 + staplesProvider 44 39 } 45 40 46 41 type routingStaplesProvider struct { 47 42 backends []backendStaplesProvider 48 43 } 49 44 45 + type dedupingStaplesProvider struct { 46 + provider staplesProvider 47 + } 48 + 50 49 func NewStaplesProvider(cfg *config.Config) (staplesProvider, error) { 51 - kclient, err := kroger.FromConfig(cfg) 52 - if err != nil { 53 - return nil, err 54 - } 55 - backends, err := defaultStaplesBackends(cfg, kclient) 50 + backends, err := defaultStaplesBackends(cfg) 56 51 if err != nil { 57 52 return nil, err 58 53 } 59 54 60 - return convertingProvider{routingStaplesProvider{ 55 + return dedupingStaplesProvider{provider: routingStaplesProvider{ 61 56 backends: backends, 62 57 }}, nil 63 58 } 64 59 65 - func (p routingStaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]kroger.Ingredient, error) { 60 + func (p routingStaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 66 61 provider, err := p.providerForLocation(locationID) 67 62 if err != nil { 68 63 return nil, err 69 64 } 65 + ctx, span := tracer.Start(ctx, "staples.fetchstaples") 66 + span.SetAttributes(attribute.String("backend", fmt.Sprintf("%T", provider))) 67 + defer span.End() 70 68 return provider.FetchStaples(ctx, locationID) 71 69 } 72 70 73 - func (p routingStaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]kroger.Ingredient, error) { 71 + func (p routingStaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 74 72 provider, err := p.providerForLocation(locationID) 75 73 if err != nil { 76 74 return nil, err 77 75 } 76 + ctx, span := tracer.Start(ctx, "staples.getingredients") 77 + span.SetAttributes(attribute.String("backend", fmt.Sprintf("%T", provider))) 78 + defer span.End() 78 79 return provider.GetIngredients(ctx, locationID, searchTerm, skip) 79 80 } 80 81 82 + func (p dedupingStaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 83 + ingredients, err := p.provider.FetchStaples(ctx, locationID) 84 + if err != nil { 85 + return nil, err 86 + } 87 + return dedupeInputIngredients(ingredients) 88 + } 89 + 90 + func (p dedupingStaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 91 + ingredients, err := p.provider.GetIngredients(ctx, locationID, searchTerm, skip) 92 + if err != nil { 93 + return nil, err 94 + } 95 + return dedupeInputIngredients(ingredients) 96 + } 97 + 81 98 type ingredientio interface { 82 99 SaveIngredients(ctx context.Context, hash string, ingredients []ai.InputIngredient) error 83 100 IngredientsFromCache(ctx context.Context, hash string) ([]ai.InputIngredient, error) ··· 98 115 GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) 99 116 } 100 117 101 - type convertingProvider struct { 102 - kprovider krogerProvider 103 - } 104 - 105 - var _ staplesProvider = convertingProvider{} 106 - 107 - func (cp convertingProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 108 - ingredients, err := cp.kprovider.FetchStaples(ctx, locationID) 109 - if err != nil { 110 - return nil, err 111 - } 112 - inputs := make([]ai.InputIngredient, 0, len(ingredients)) 118 + func dedupeInputIngredients(ingredients []ai.InputIngredient) ([]ai.InputIngredient, error) { 119 + seen := map[string]bool{} 120 + var deduped []ai.InputIngredient 113 121 for _, ingredient := range ingredients { 114 - input, err := inputIngredientFromKrogerIngredient(ingredient) 115 - if err != nil { 116 - return nil, err 122 + if ingredient.ProductID == "" { 123 + return nil, fmt.Errorf("blank product id for ingredient: %+v", ingredient) 117 124 } 118 - inputs = append(inputs, input) 119 - } 120 - 121 - inputs = lo.UniqBy(inputs, func(i ai.InputIngredient) string { 122 - return i.ProductID 123 - }) 124 - return inputs, nil 125 - } 126 - 127 - func (cp convertingProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 128 - ingredients, err := cp.kprovider.GetIngredients(ctx, locationID, searchTerm, skip) 129 - if err != nil { 130 - return nil, err 131 - } 132 - inputs := make([]ai.InputIngredient, 0, len(ingredients)) 133 - for _, ingredient := range ingredients { 134 - input, err := inputIngredientFromKrogerIngredient(ingredient) 135 - if err != nil { 136 - return nil, err 125 + if seen[ingredient.ProductID] { 126 + continue 137 127 } 138 - inputs = append(inputs, input) 128 + seen[ingredient.ProductID] = true 129 + deduped = append(deduped, ingredient) 139 130 } 140 - 141 - inputs = lo.UniqBy(inputs, func(i ai.InputIngredient) string { 142 - return i.ProductID 143 - }) 144 - return inputs, nil 145 - } 146 - 147 - func inputIngredientFromKrogerIngredient(ingredient kroger.Ingredient) (ai.InputIngredient, error) { 148 - item := ai.InputIngredient{ 149 - ProductID: strings.TrimSpace(toStr(ingredient.ProductId)), 150 - AisleNumber: strings.TrimSpace(toStr(ingredient.AisleNumber)), 151 - Brand: strings.TrimSpace(toStr(ingredient.Brand)), 152 - Description: strings.TrimSpace(toStr(ingredient.Description)), 153 - Size: strings.TrimSpace(toStr(ingredient.Size)), 154 - PriceRegular: clonePrice(ingredient.PriceRegular), 155 - PriceSale: clonePrice(ingredient.PriceSale), 156 - Categories: categoriesFromPtr(ingredient.Categories), 157 - } 158 - item = ai.NormalizeInputIngredient(item) 159 - if item.ProductID == "" { 160 - return ai.InputIngredient{}, fmt.Errorf("ingredient product_id is required for %q", toStr(ingredient.Description)) 161 - } 162 - return item, nil 163 - } 164 - 165 - func toStr(ptr *string) string { 166 - if ptr == nil { 167 - return "" 168 - } 169 - return *ptr 170 - } 171 - 172 - func categoriesFromPtr(ptr *[]string) []string { 173 - if ptr == nil { 174 - return nil 175 - } 176 - return append([]string(nil), (*ptr)...) 177 - } 178 - 179 - func clonePrice(price *float32) *float32 { 180 - if price == nil { 181 - return nil 182 - } 183 - value := *price 184 - return &value 131 + return deduped, nil 185 132 } 186 133 187 134 func NewCachedStaplesService(cfg *config.Config, c cache.Cache, grader grader) (*cachedStaplesService, error) { ··· 217 164 return nil, fmt.Errorf("failed to get ingredients for staples for %s: %w", locationID, err) 218 165 } 219 166 167 + ctx, span := tracer.Start(ctx, "staples.gradeingredients") 168 + defer span.End() 220 169 graded, err := s.grader.GradeIngredients(ctx, ingredients) 221 170 if err != nil { 222 171 slog.ErrorContext(ctx, "failed to grade cached staples", "error", err) ··· 303 252 return nil, fmt.Errorf("staples provider does not support location %q", locationID) 304 253 } 305 254 306 - func defaultStaplesBackends(cfg *config.Config, krogerClient kroger.ClientWithResponsesInterface) ([]backendStaplesProvider, error) { 255 + // should we pass in a wrapper/roundtripper 256 + func defaultStaplesBackends(cfg *config.Config) ([]backendStaplesProvider, error) { 307 257 // should we do this per request so we get new proxies per user? https://github.com/paulgmiller/careme/issues/443 308 - httpClient, err := brightdata.NewProxyAwareHTTPClient(cfg.BrightDataProxy) 258 + brightdataClient, err := brightdata.NewProxyAwareHTTPClient(cfg.BrightDataProxy) 309 259 if err != nil { 310 260 return nil, fmt.Errorf("create bright data proxy-aware client: %w", err) 311 261 } 262 + brightdataClient.Transport = otelhttp.NewTransport(brightdataClient.Transport) 312 263 313 264 // only returns an err because it ensures a cache for reese84 tokens. 314 - albertsonsProvider, err := albertsons.NewStaplesProvider(cfg.Albertsons, httpClient) 265 + albertsonsProvider, err := albertsons.NewStaplesProvider(cfg.Albertsons, brightdataClient) 315 266 if err != nil { 316 267 return nil, fmt.Errorf("create albertsons staples provider: %w", err) 317 268 } 318 269 270 + // we should not use brightdata for koger 271 + httpClient := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} 272 + krogerBackend, err := kroger.NewStaplesProvider(cfg, httpClient) 273 + if err != nil { 274 + return nil, fmt.Errorf("create kroger staples provider: %w", err) 275 + } 276 + 319 277 return []backendStaplesProvider{ 320 - kroger.NewStaplesProvider(krogerClient), 321 278 albertsonsProvider, 279 + krogerBackend, 322 280 // actowiz.NewStaplesProvider(), 323 281 walmart.NewStaplesProvider(), 324 - wholefoods.NewStaplesProvider(wholefoods.NewClient(httpClient)), 282 + wholefoods.NewStaplesProvider(wholefoods.NewClient(brightdataClient)), 325 283 }, nil 326 284 } 327 285
+88 -62
internal/recipes/staples_test.go
··· 10 10 "careme/internal/ai" 11 11 "careme/internal/albertsons" 12 12 "careme/internal/cache" 13 - "careme/internal/kroger" 14 13 "careme/internal/locations" 15 14 ) 16 15 17 16 type stubStaplesProvider struct { 18 17 ids map[string]bool 19 - ingredients []kroger.Ingredient 18 + ingredients []ai.InputIngredient 20 19 err error 21 20 calls int 22 21 } ··· 29 28 return "stub-staples-v1" 30 29 } 31 30 32 - func (s *stubStaplesProvider) FetchStaples(_ context.Context, _ string) ([]kroger.Ingredient, error) { 31 + func (s *stubStaplesProvider) FetchStaples(_ context.Context, _ string) ([]ai.InputIngredient, error) { 33 32 s.calls++ 34 33 if s.err != nil { 35 34 return nil, s.err ··· 37 36 return slices.Clone(s.ingredients), nil 38 37 } 39 38 40 - func (s *stubStaplesProvider) GetIngredients(_ context.Context, _ string, _ string, _ int) ([]kroger.Ingredient, error) { 39 + func (s *stubStaplesProvider) GetIngredients(_ context.Context, _ string, _ string, _ int) ([]ai.InputIngredient, error) { 41 40 return s.FetchStaples(context.Background(), "") 42 41 } 43 42 ··· 86 85 } 87 86 88 87 func TestRoutingStaplesProvider_SelectsProviderByLocationID(t *testing.T) { 89 - krogerProvider := &stubStaplesProvider{ids: map[string]bool{"70100023": true}} 88 + krogerBackend := &stubStaplesProvider{ids: map[string]bool{"70100023": true}} 90 89 wholeFoodsProvider := &stubStaplesProvider{ids: map[string]bool{"wholefoods_10216": true}} 91 90 provider := routingStaplesProvider{ 92 - backends: []backendStaplesProvider{krogerProvider, wholeFoodsProvider}, 91 + backends: []backendStaplesProvider{krogerBackend, wholeFoodsProvider}, 93 92 } 94 93 95 94 if _, err := provider.FetchStaples(t.Context(), "70100023"); err != nil { 96 95 t.Fatalf("FetchStaples kroger returned error: %v", err) 97 96 } 98 - if krogerProvider.calls != 1 || wholeFoodsProvider.calls != 0 { 99 - t.Fatalf("expected kroger provider to be selected, got kroger=%d wholefoods=%d", krogerProvider.calls, wholeFoodsProvider.calls) 97 + if krogerBackend.calls != 1 || wholeFoodsProvider.calls != 0 { 98 + t.Fatalf("expected kroger provider to be selected, got kroger=%d wholefoods=%d", krogerBackend.calls, wholeFoodsProvider.calls) 100 99 } 101 100 102 101 if _, err := provider.FetchStaples(t.Context(), "wholefoods_10216"); err != nil { 103 102 t.Fatalf("FetchStaples whole foods returned error: %v", err) 104 103 } 105 - if krogerProvider.calls != 1 || wholeFoodsProvider.calls != 1 { 106 - t.Fatalf("expected whole foods provider to be selected once, got kroger=%d wholefoods=%d", krogerProvider.calls, wholeFoodsProvider.calls) 104 + if krogerBackend.calls != 1 || wholeFoodsProvider.calls != 1 { 105 + t.Fatalf("expected whole foods provider to be selected once, got kroger=%d wholefoods=%d", krogerBackend.calls, wholeFoodsProvider.calls) 107 106 } 108 107 } 109 108 ··· 125 124 } 126 125 127 126 func TestRoutingStaplesProvider_GetIngredients_SelectsProviderByLocationID(t *testing.T) { 128 - krogerProvider := &stubStaplesProvider{ 127 + krogerBackend := &stubStaplesProvider{ 129 128 ids: map[string]bool{"70100023": true}, 130 - ingredients: []kroger.Ingredient{{Description: loPtr("Pinot Noir")}}, 129 + ingredients: []ai.InputIngredient{{ProductID: "1", Description: "Pinot Noir"}}, 131 130 } 132 131 wholeFoodsProvider := &stubStaplesProvider{ 133 132 ids: map[string]bool{"wholefoods_10216": true}, 134 - ingredients: []kroger.Ingredient{{Description: loPtr("Whole Foods Pinot Noir")}}, 133 + ingredients: []ai.InputIngredient{{ProductID: "2", Description: "Whole Foods Pinot Noir"}}, 135 134 } 136 135 provider := routingStaplesProvider{ 137 - backends: []backendStaplesProvider{krogerProvider, wholeFoodsProvider}, 136 + backends: []backendStaplesProvider{krogerBackend, wholeFoodsProvider}, 138 137 } 139 138 140 139 got, err := provider.GetIngredients(t.Context(), "wholefoods_10216", "pinot noir", 0) 141 140 if err != nil { 142 141 t.Fatalf("GetIngredients returned error: %v", err) 143 142 } 144 - if len(got) != 1 || got[0].Description == nil || *got[0].Description != "Whole Foods Pinot Noir" { 143 + if len(got) != 1 || got[0].Description != "Whole Foods Pinot Noir" { 145 144 t.Fatalf("unexpected ingredients: %+v", got) 146 145 } 147 - if krogerProvider.calls != 0 || wholeFoodsProvider.calls != 1 { 148 - t.Fatalf("expected whole foods provider to be selected, got kroger=%d wholefoods=%d", krogerProvider.calls, wholeFoodsProvider.calls) 146 + if krogerBackend.calls != 0 || wholeFoodsProvider.calls != 1 { 147 + t.Fatalf("expected whole foods provider to be selected, got kroger=%d wholefoods=%d", krogerBackend.calls, wholeFoodsProvider.calls) 149 148 } 150 149 } 151 150 152 - func TestInputIngredientFromKrogerIngredientMapsFields(t *testing.T) { 153 - regular := float32(4.99) 154 - sale := float32(3.49) 155 - categories := []string{"Produce", "Fresh Fruit"} 156 - ingredient, err := inputIngredientFromKrogerIngredient(kroger.Ingredient{ 157 - ProductId: loPtr(" apple-1 "), 158 - AisleNumber: loPtr(" 12 "), 159 - Brand: loPtr(" Orchard Co "), 160 - Description: loPtr(" Honeycrisp Apple "), 161 - Size: loPtr(" 3 lb "), 162 - PriceRegular: &regular, 163 - PriceSale: &sale, 164 - Categories: &categories, 165 - }) 151 + func TestRoutingStaplesProvider_DoesNotDedupeIngredients(t *testing.T) { 152 + backend := &stubStaplesProvider{ 153 + ids: map[string]bool{"70100023": true}, 154 + ingredients: []ai.InputIngredient{ 155 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 156 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 157 + }, 158 + } 159 + provider := routingStaplesProvider{ 160 + backends: []backendStaplesProvider{backend}, 161 + } 162 + 163 + got, err := provider.FetchStaples(t.Context(), "70100023") 166 164 if err != nil { 167 - t.Fatalf("inputIngredientFromKrogerIngredient returned error: %v", err) 165 + t.Fatalf("FetchStaples returned error: %v", err) 166 + } 167 + if len(got) != 2 { 168 + t.Fatalf("expected routing provider to preserve backend ingredients, got %+v", got) 168 169 } 170 + } 169 171 170 - if ingredient.ProductID != "apple-1" { 171 - t.Fatalf("unexpected product id: %+v", ingredient) 172 + func TestDedupingStaplesProvider_FetchStaplesDedupesProductIDs(t *testing.T) { 173 + provider := dedupingStaplesProvider{ 174 + provider: &stubRoutingStaplesProvider{ 175 + ingredients: []ai.InputIngredient{ 176 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 177 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 178 + {ProductID: "spinach-1", Description: "Baby Spinach"}, 179 + }, 180 + }, 172 181 } 173 - if ingredient.AisleNumber != "12" || ingredient.Brand != "Orchard Co" || ingredient.Description != "Honeycrisp Apple" || ingredient.Size != "3 lb" { 174 - t.Fatalf("unexpected normalized ingredient: %+v", ingredient) 182 + 183 + got, err := provider.FetchStaples(t.Context(), "70100023") 184 + if err != nil { 185 + t.Fatalf("FetchStaples returned error: %v", err) 175 186 } 176 - if ingredient.PriceRegular == nil || *ingredient.PriceRegular != regular { 177 - t.Fatalf("unexpected regular price: %+v", ingredient.PriceRegular) 187 + if len(got) != 2 { 188 + t.Fatalf("expected deduped ingredients, got %+v", got) 178 189 } 179 - if ingredient.PriceSale == nil || *ingredient.PriceSale != sale { 180 - t.Fatalf("unexpected sale price: %+v", ingredient.PriceSale) 190 + if got[0].ProductID != "apple-1" || got[1].ProductID != "spinach-1" { 191 + t.Fatalf("expected first occurrence of each product id in order, got %+v", got) 181 192 } 182 - if !slices.Equal(ingredient.Categories, categories) { 183 - t.Fatalf("unexpected categories: got %v want %v", ingredient.Categories, categories) 193 + } 194 + 195 + func TestDedupingStaplesProvider_GetIngredientsDedupesProductIDs(t *testing.T) { 196 + provider := dedupingStaplesProvider{ 197 + provider: &stubRoutingStaplesProvider{ 198 + ingredients: []ai.InputIngredient{ 199 + {ProductID: "wine-1", Description: "Pinot Noir"}, 200 + {ProductID: "wine-1", Description: "Pinot Noir"}, 201 + {ProductID: "wine-2", Description: "Cabernet Sauvignon"}, 202 + }, 203 + }, 204 + } 205 + 206 + got, err := provider.GetIngredients(t.Context(), "70100023", "pinot noir", 0) 207 + if err != nil { 208 + t.Fatalf("GetIngredients returned error: %v", err) 209 + } 210 + if len(got) != 2 { 211 + t.Fatalf("expected deduped ingredients, got %+v", got) 212 + } 213 + if got[0].ProductID != "wine-1" || got[1].ProductID != "wine-2" { 214 + t.Fatalf("expected first occurrence of each product id in order, got %+v", got) 184 215 } 185 216 } 186 217 187 - func TestInputIngredientFromKrogerIngredientRejectsBlankProductID(t *testing.T) { 188 - _, err := inputIngredientFromKrogerIngredient(kroger.Ingredient{Description: loPtr("Asparagus")}) 218 + func TestDedupingStaplesProvider_RejectsBlankProductID(t *testing.T) { 219 + provider := dedupingStaplesProvider{ 220 + provider: &stubRoutingStaplesProvider{ 221 + ingredients: []ai.InputIngredient{{Description: "Mystery Ingredient"}}, 222 + }, 223 + } 224 + 225 + _, err := provider.FetchStaples(t.Context(), "70100023") 189 226 if err == nil { 190 227 t.Fatal("expected blank product id error") 191 228 } 192 - if !strings.Contains(err.Error(), "product_id is required") { 229 + if !strings.Contains(err.Error(), "blank product id for ingredient") { 193 230 t.Fatalf("unexpected error: %v", err) 194 231 } 195 232 } ··· 208 245 cacheStore := cache.NewFileCache(t.TempDir()) 209 246 provider := &stubStaplesProvider{ 210 247 ids: map[string]bool{"wholefoods_10216": true}, 211 - ingredients: []kroger.Ingredient{ 212 - {ProductId: loPtr("apple-1"), Description: loPtr("Honeycrisp Apple")}, 213 - {ProductId: loPtr("apple-1"), Description: loPtr("Honeycrisp Apple")}, 214 - {ProductId: loPtr("spinach-1"), Description: loPtr("Baby Spinach")}, 248 + ingredients: []ai.InputIngredient{ 249 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 250 + {ProductID: "apple-1", Description: "Honeycrisp Apple"}, 251 + {ProductID: "spinach-1", Description: "Baby Spinach"}, 215 252 }, 216 253 } 217 254 s := &cachedStaplesService{ 218 255 cache: IO(cacheStore), 219 - provider: convertingProvider{kprovider: provider}, 256 + provider: provider, 220 257 grader: &stubIngredientGrader{}, 221 258 } 222 259 ··· 232 269 if provider.calls != 1 { 233 270 t.Fatalf("expected provider to be called once, got %d", provider.calls) 234 271 } 235 - if len(got) != 2 { 236 - t.Fatalf("expected deduped results, got %d", len(got)) 237 - } 238 272 if got[0].ProductID == "" { 239 273 t.Fatalf("expected input ingredient product id, got %+v", got) 240 274 } 241 275 242 - cached, err := IO(cacheStore).IngredientsFromCache(t.Context(), params.LocationHash()) 243 - if err != nil { 244 - t.Fatalf("IngredientsFromCache returned error: %v", err) 245 - } 246 - if len(cached) != 2 { 247 - t.Fatalf("expected cached deduped results, got %d", len(cached)) 248 - } 249 - 250 276 gotAgain, err := s.FetchStaples(t.Context(), params) 251 277 if err != nil { 252 278 t.Fatalf("FetchStaples returned error on cached call: %v", err) ··· 254 280 if provider.calls != 1 { 255 281 t.Fatalf("expected cached call to skip provider, got %d calls", provider.calls) 256 282 } 257 - if len(gotAgain) != 2 { 283 + if len(gotAgain) != 3 { 258 284 t.Fatalf("expected cached results, got %d", len(gotAgain)) 259 285 } 260 286 }
+1
internal/templates/about.go
··· 77 77 {Comment: "half eaten lamb chopps", ImageID: "AP1GczOMjRr7tmZ5xxPzXsUHOip34t32QHfZJrQFsj6bo_FeU3P38DoRsHP-iAqxZMgj_WPE3XiJRMpDM6ezi8f8Q1pd1d92EnFacQF-vkgy6qv2ULgct8qh", RecipeHash: "cbycxIAij_RK6vD4BfptFQ=="}, 78 78 {Comment: "chicken thights fennel and carrots", ImageID: "AP1GczO4qkM5zaXym17qH8Cy2IpWW_SHdWDmkKMiRx_VcN4ZBG9_dwI3ybDdri2v8n9XFNdCprnv72kD2JCwMnSkz38Mqa95OORDDjppLMGimj0DLbQATOf3", RecipeHash: "7AvK-N9pE6lJY0S40JK23A=="}, 79 79 {Comment: "sausage, mushrooms and kale pasta", ImageID: "AP1GczMxEf7tY7cpxOuJnHlzJw48xq-JtP_x5XVjNCzs8m_a6HuizPEVgjWKsuVs84WNwa181arukeILhn32Lx6u_XwjDamwRUMnVylxChG8i7K_-fK56ztG", RecipeHash: "HZsnsGnH739VEKUrE18KGg=="}, 80 + {Comment: "Tomato Basil Salmon and Sweet Potato Mash", ImageID: "AP1GczNEyUDLKFjvq-MkU1NXbNTrLss1qybDKLggOjzz96uSZ3MeCJloup4WSCQnx_1OEYPeZIzyHDvZJTKngMVsAw23z5Qwu3o1sdQ2iClGgdtqA05E9TTF", RecipeHash: "bqkbPEAEaKH-P0IgDogzcQ=="}, 80 81 }
+3 -3
internal/walmart/staples.go
··· 5 5 "fmt" 6 6 "strings" 7 7 8 - "careme/internal/kroger" 8 + "careme/internal/ai" 9 9 ) 10 10 11 11 const UnsupportedStaplesSignature = "unsupported-staples-v1" ··· 48 48 return UnsupportedStaplesSignature 49 49 } 50 50 51 - func (p StaplesProvider) FetchStaples(_ context.Context, locationID string) ([]kroger.Ingredient, error) { 51 + func (p StaplesProvider) FetchStaples(_ context.Context, locationID string) ([]ai.InputIngredient, error) { 52 52 return nil, fmt.Errorf("staples provider does not support location %q", locationID) 53 53 } 54 54 55 - func (p StaplesProvider) GetIngredients(_ context.Context, locationID string, searchTerm string, skip int) ([]kroger.Ingredient, error) { 55 + func (p StaplesProvider) GetIngredients(_ context.Context, locationID string, searchTerm string, skip int) ([]ai.InputIngredient, error) { 56 56 return nil, fmt.Errorf("ingredient search is not supported for location %q and term %q", locationID, searchTerm) 57 57 }
+4 -1
internal/wholefoods/client.go
··· 145 145 baseURL = DefaultBaseURL 146 146 } 147 147 if httpClient == nil { 148 - httpClient = &http.Client{Timeout: 20 * time.Second} 148 + httpClient = http.DefaultClient 149 149 } 150 150 151 151 return &client{ ··· 157 157 // Category fetches category products and follows limit/offset pagination until 158 158 // the API returns fewer items than the requested page size. 159 159 func (c *client) Category(ctx context.Context, queryterm, store string) ([]product, error) { 160 + ctx, cancel := context.WithTimeout(ctx, time.Second*20) 161 + defer cancel() 162 + 160 163 queryterm = strings.TrimSpace(queryterm) 161 164 if queryterm == "" { 162 165 return nil, errors.New("queryterm is required")
+16 -29
internal/wholefoods/staples.go
··· 9 9 "log/slog" 10 10 "strings" 11 11 12 - "careme/internal/kroger" 12 + "careme/internal/ai" 13 13 "careme/internal/parallelism" 14 14 15 15 "github.com/samber/lo" ··· 45 45 return ok 46 46 } 47 47 48 - func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]kroger.Ingredient, error) { 48 + func (p StaplesProvider) FetchStaples(ctx context.Context, locationID string) ([]ai.InputIngredient, error) { 49 49 if p.client == nil { 50 50 return nil, fmt.Errorf("whole foods client is required") 51 51 } ··· 56 56 return nil, fmt.Errorf("invalid whole foods location id %q", locationID) 57 57 } 58 58 59 - return parallelism.Flatten(defaultStaples(), func(category string) ([]kroger.Ingredient, error) { 59 + return parallelism.Flatten(defaultStaples(), func(category string) ([]ai.InputIngredient, error) { 60 60 resp, err := p.client.Category(ctx, category, storeID) 61 61 if err != nil { 62 62 slog.WarnContext(ctx, "Failed to fetch category", "category", category, "location", locationID, "error", err) 63 63 return nil, err 64 64 } 65 65 66 - ingredients := lo.Map(resp, func(product product, _ int) kroger.Ingredient { 67 - return productToIngredient(product) 68 - }) 66 + ingredients := lo.Map(resp, productToIngredient) 69 67 slog.InfoContext(ctx, "Found ingredients for category", "count", len(ingredients), "category", category, "location", locationID) 70 68 71 69 return ingredients, nil 72 70 }) 73 71 } 74 72 75 - func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, skip int) ([]kroger.Ingredient, error) { 73 + func (p StaplesProvider) GetIngredients(ctx context.Context, locationID string, searchTerm string, _ int) ([]ai.InputIngredient, error) { 76 74 if p.client == nil { 77 75 return nil, fmt.Errorf("whole foods client is required") 78 76 } ··· 82 80 return nil, fmt.Errorf("invalid whole foods location id %q", locationID) 83 81 } 84 82 83 + // no pagination so no skip 85 84 resp, err := p.client.Category(ctx, searchTerm, storeID) 86 85 if err != nil { 87 86 return nil, err 88 87 } 89 88 90 - ingredients := lo.Map(resp, func(product product, _ int) kroger.Ingredient { 91 - return productToIngredient(product) 92 - }) 93 - if skip >= len(ingredients) { 94 - return []kroger.Ingredient{}, nil 95 - } 96 - return ingredients[skip:], nil 89 + return lo.Map(resp, productToIngredient), nil 97 90 } 98 91 99 92 func defaultStaples() []string { ··· 108 101 "shellfish", 109 102 "goat-lamb-veal", 110 103 "game-meats", 104 + "rice-grains", 105 + "pasta-noodles", 111 106 } 112 - // rice-grains? 113 - // pasta-noodles 114 107 // red-wine, white-wine, sparkling 115 108 } 116 109 117 - func productToIngredient(product product) kroger.Ingredient { 110 + func productToIngredient(product product, _ int) ai.InputIngredient { 118 111 var regularPrice *float32 119 112 if product.RegularPrice > 0 { 120 113 price := float32(product.RegularPrice) ··· 138 131 // categories := compactStrings(localCategory(product)) 139 132 140 133 hasher := fnv.New32a() 134 + // dupes for different units of measure? 141 135 _ = lo.Must(hasher.Write([]byte(product.Slug))) 142 136 productId := base64.RawURLEncoding.EncodeToString(hasher.Sum(nil)) 143 - return kroger.Ingredient{ 144 - ProductId: stringPtr(productId), 145 - Brand: stringPtr(strings.TrimSpace(product.Brand)), 146 - Description: stringPtr(strings.TrimSpace(product.Name)), 137 + return ai.NormalizeInputIngredient(ai.InputIngredient{ 138 + ProductID: productId, 139 + Brand: strings.TrimSpace(product.Brand), 140 + Description: strings.TrimSpace(product.Name), 147 141 // Size: size, 148 142 PriceRegular: regularPrice, 149 143 PriceSale: salePrice, 150 144 // / Categories: slicePtr(categories), 151 - } 152 - } 153 - 154 - func stringPtr(value string) *string { 155 - if value == "" { 156 - return nil 157 - } 158 - return &value 145 + }) 159 146 }
+8 -8
internal/wholefoods/staples_test.go
··· 79 79 } 80 80 81 81 ingredient := got[0] 82 - if ingredient.Description == nil || *ingredient.Description != "Organic Asparagus" { 82 + if ingredient.Description != "Organic Asparagus" { 83 83 t.Fatalf("unexpected description: %+v", ingredient.Description) 84 84 } 85 - if ingredient.Brand == nil || *ingredient.Brand != "Whole Foods Market" { 85 + if ingredient.Brand != "Whole Foods Market" { 86 86 t.Fatalf("unexpected brand: %+v", ingredient.Brand) 87 87 } 88 - if ingredient.ProductId == nil || *ingredient.ProductId != "odQxPA" { 89 - t.Fatalf("unexpected product id: %+v", *ingredient.ProductId) 88 + if ingredient.ProductID != "odQxPA" { 89 + t.Fatalf("unexpected product id: %+v", ingredient.ProductID) 90 90 } 91 91 if ingredient.PriceRegular == nil || *ingredient.PriceRegular != float32(5.99) { 92 92 t.Fatalf("unexpected regular price: %+v", ingredient.PriceRegular) ··· 122 122 } 123 123 provider := NewStaplesProvider(client) 124 124 125 - got, err := provider.GetIngredients(t.Context(), "wholefoods_10216", "pinot noir", 1) 125 + got, err := provider.GetIngredients(t.Context(), "wholefoods_10216", "pinot noir", 0) 126 126 if err != nil { 127 127 t.Fatalf("GetIngredients returned error: %v", err) 128 128 } 129 - if len(got) != 1 { 130 - t.Fatalf("expected 1 ingredient after skip, got %d", len(got)) 129 + if len(got) != 2 { 130 + t.Fatalf("expected 2 ingredients, got %d", len(got)) 131 131 } 132 - if got[0].Description == nil || *got[0].Description != "Rose" { 132 + if got[0].Description != "Pinot Noir" { 133 133 t.Fatalf("unexpected ingredient description: %+v", got[0].Description) 134 134 } 135 135 if got := client.callCount(); got != 1 {