@crvouga/mockingbird-service-edamam 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Changelog — @crvouga/mockingbird-service-edamam
2
2
 
3
- ## 0.2.0 (2026-09-24)
3
+ ## 1.0.1 (2026-09-26)
4
+
5
+ ### Fixes and improvements
6
+
7
+ - take the rejected-request byte count from content-length, never by reading the body ([a0e5fb2](https://github.com/crvouga/mockingbird/commit/a0e5fb2fc80ab725c3552b612f3f091fd6c857fb))
8
+ - reject a present body by media type, and log what a rejected request sent ([12e351a](https://github.com/crvouga/mockingbird/commit/12e351aba53cfb7b41e31a7809419455fc2902eb))
9
+
10
+ ## 1.0.0 (2026-09-24)
11
+
12
+ ### ⚠️ Breaking changes
13
+
14
+ - vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
15
+
16
+ ## 0.2.0 (2026-09-23)
4
17
 
5
18
  ### Features
6
19
 
package/README.md CHANGED
@@ -10,7 +10,7 @@ tests without Edamam keys, quotas or network.
10
10
  - Operation coverage: [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/edamam/SUPPORT.md)
11
11
  - The contract (`openapi.yaml`) is hand-authored from Edamam's per-API docs and our consumers:
12
12
  `metrics/adapters/outbound/edamam-nutrition.adapter.ts`,
13
- `meal-planning/adapters/outbound/edamam-meal-planning.adapter.ts`, and the Makor chat
13
+ `meal-planning/adapters/outbound/edamam-meal-planning.adapter.ts`, and the Python chat
14
14
  service's `tools/nutrition/client.py`.
15
15
 
16
16
  ## Install
@@ -26,9 +26,9 @@ any Fetch server.
26
26
  ## Usage
27
27
 
28
28
  Both backend adapters hardcode `https://api.edamam.com` (seam: a base-URL env for
29
- `EDAMAM_BASE_URL` / `EDAMAM_MEAL_BASE_URL`, and the Makor client's `EDAMAM_BASE_URL`). Keys can be
30
- any values (`EDAMAM_FOOD_APP_ID/KEY` or `EDAMAM_APP_ID/KEY`, `EDAMAM_MEAL_APP_ID/KEY`, the Makor
31
- `edamam_*` settings); without them our adapters report `unavailable` and never call out.
29
+ `EDAMAM_BASE_URL` / `EDAMAM_MEAL_BASE_URL`, and the Python client's `EDAMAM_BASE_URL`). Keys can be
30
+ any values (`EDAMAM_FOOD_APP_ID/KEY` or `EDAMAM_APP_ID/KEY`, `EDAMAM_MEAL_APP_ID/KEY`, the Python
31
+ client's `edamam_*` settings); without them our adapters report `unavailable` and never call out.
32
32
 
33
33
  ```bash
34
34
  npx mockingbird-edamam serve --port 8824
@@ -90,7 +90,7 @@ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`): `rate_
90
90
  ### Namespaces
91
91
 
92
92
  `x-mockingbird-namespace`, a `/ns/<name>` prefix on the base URL (works for the nutrition adapter
93
- and the Makor client, which concatenate paths; the meal adapter resolves paths with
93
+ and the Python client, which concatenate paths; the meal adapter resolves paths with
94
94
  `new URL(endpoint, base)`, which drops a prefix), or by application id:
95
95
  `PUT /__admin/credentials {"credentials": {"<app_id>": "<namespace>"}}`.
96
96
 
@@ -1358,6 +1358,90 @@ var operationMetadata = (operation) => {
1358
1358
 
1359
1359
  // ../core/dist/service.js
1360
1360
  import { Hono } from "hono";
1361
+
1362
+ // ../core/dist/validation.js
1363
+ var requestBodyOf = (context) => {
1364
+ const requestBody = context.operation.operation.requestBody;
1365
+ return requestBody ? deref(context.document, requestBody) : void 0;
1366
+ };
1367
+ var mediaTypeMatches = (accepted, actual) => {
1368
+ const pattern = mediaTypeOf(accepted) ?? accepted.toLowerCase();
1369
+ if (pattern === actual || pattern === "*/*")
1370
+ return true;
1371
+ const [type, subtype] = pattern.split("/");
1372
+ const [actualType, actualSubtype] = actual.split("/");
1373
+ if (type !== actualType && type !== "*")
1374
+ return false;
1375
+ if (subtype === void 0 || subtype === "*")
1376
+ return true;
1377
+ if (subtype.startsWith("*+"))
1378
+ return actualSubtype?.endsWith(subtype.slice(1)) === true;
1379
+ return false;
1380
+ };
1381
+ var acceptedKeyFor = (content, mediaType) => {
1382
+ if (!content || mediaType === void 0)
1383
+ return void 0;
1384
+ const keys = Object.keys(content);
1385
+ return keys.find((key) => mediaTypeOf(key) === mediaType) ?? keys.find((key) => mediaTypeMatches(key, mediaType));
1386
+ };
1387
+ var unsupportedMediaType = (context, options = {}) => {
1388
+ if (context.body.kind === "empty" && options.includeEmpty !== true)
1389
+ return void 0;
1390
+ const content = requestBodyOf(context)?.content;
1391
+ if (!content || Object.keys(content).length === 0)
1392
+ return void 0;
1393
+ const mediaType = mediaTypeOf(context.request.headers.get("content-type"));
1394
+ if (acceptedKeyFor(content, mediaType) !== void 0)
1395
+ return void 0;
1396
+ return { mediaType: mediaType ?? null, accepted: Object.keys(content) };
1397
+ };
1398
+ var recorded = /* @__PURE__ */ new WeakMap();
1399
+ var recordedIssues = (request) => recorded.get(request);
1400
+ var bodyIssues = (context, contentType = "application/json") => {
1401
+ const issues = findBodyIssues(context, contentType);
1402
+ if (issues.length > 0)
1403
+ recorded.set(context.request, issues);
1404
+ return issues;
1405
+ };
1406
+ var findBodyIssues = (context, contentType) => {
1407
+ const resolved = requestBodyOf(context);
1408
+ if (!resolved)
1409
+ return [];
1410
+ const unsupported = unsupportedMediaType(context);
1411
+ if (unsupported) {
1412
+ return [
1413
+ {
1414
+ path: "",
1415
+ kind: "media_type",
1416
+ message: unsupported.mediaType === null ? "request body has no content-type" : `request body media type ${unsupported.mediaType} is not supported`
1417
+ }
1418
+ ];
1419
+ }
1420
+ const key = acceptedKeyFor(resolved.content, mediaTypeOf(context.request.headers.get("content-type"))) ?? contentType;
1421
+ const schema = resolved.content?.[key]?.schema;
1422
+ if (!schema)
1423
+ return [];
1424
+ if (context.body.kind === "invalid") {
1425
+ return [
1426
+ {
1427
+ path: "",
1428
+ kind: "syntax",
1429
+ message: `request body is not valid ${context.body.mediaType}`
1430
+ }
1431
+ ];
1432
+ }
1433
+ const value = context.body.kind === "json" || context.body.kind === "form" || context.body.kind === "text" ? context.body.value : void 0;
1434
+ if (value === void 0) {
1435
+ return resolved.required && context.body.kind === "empty" ? [{ path: "", kind: "required", message: "request body is required" }] : [];
1436
+ }
1437
+ return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), kind: "schema", message: issue.message }));
1438
+ };
1439
+
1440
+ // ../core/dist/service.js
1441
+ var withIssues = (request, response) => {
1442
+ const issues = response.status >= 400 ? recordedIssues(request) : void 0;
1443
+ return issues && issues.length > 0 ? annotateResponse(response, { issues }) : response;
1444
+ };
1361
1445
  var defineOperations = (handlers) => handlers;
1362
1446
  var OperationRegistryError = class extends Error {
1363
1447
  problems;
@@ -1443,9 +1527,7 @@ var createService = (options) => {
1443
1527
  now
1444
1528
  };
1445
1529
  const short = await options.before?.(context);
1446
- if (short)
1447
- return short;
1448
- return handler(context);
1530
+ return withIssues(request, short ?? await handler(context));
1449
1531
  };
1450
1532
  app.on(operation.method.toUpperCase(), honoPath(operation.path), route);
1451
1533
  }
@@ -1482,7 +1564,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
1482
1564
  };
1483
1565
 
1484
1566
  // ../core/dist/version.js
1485
- var PACKAGE_VERSION = true ? "0.2.0" : UNRELEASED_VERSION;
1567
+ var PACKAGE_VERSION = true ? "1.0.1" : UNRELEASED_VERSION;
1486
1568
 
1487
1569
  // ../core/dist/signing.js
1488
1570
  var encoder = new TextEncoder();
@@ -1933,8 +2015,15 @@ var createRuntime = (options) => {
1933
2015
  const started = monotonicNow();
1934
2016
  const url = new URL(request.url);
1935
2017
  const operationId = operationIdFor(request, url.pathname);
2018
+ const contentLength = request.headers.get("content-length");
2019
+ const received = {
2020
+ contentType: request.headers.get("content-type"),
2021
+ bodyBytes: contentLength !== null && /^\d+$/.test(contentLength) ? Number(contentLength) : null,
2022
+ transferEncoding: request.headers.get("transfer-encoding")
2023
+ };
1936
2024
  const log = (status, faultId, response2) => {
1937
2025
  const noted = response2 ? responseNotes(response2) : void 0;
2026
+ const rejected = status >= 400 && faultId === void 0;
1938
2027
  const entry = {
1939
2028
  service: options.name,
1940
2029
  namespace,
@@ -1946,7 +2035,9 @@ var createRuntime = (options) => {
1946
2035
  unmatched: options.document !== void 0 && operationId === void 0,
1947
2036
  ...faultId !== void 0 ? { faultId } : {},
1948
2037
  ...noted?.ids && Object.keys(noted.ids).length > 0 ? { ids: noted.ids } : {},
1949
- ...noted?.adopted ? { adopted: true } : {}
2038
+ ...noted?.adopted ? { adopted: true } : {},
2039
+ ...rejected ? { request: { ...received } } : {},
2040
+ ...rejected && noted?.issues && noted.issues.length > 0 ? { issues: noted.issues.map((issue) => ({ ...issue })) } : {}
1950
2041
  };
1951
2042
  metrics.record(entry);
1952
2043
  journal.record({ ...entry, at: new Date(clock.now()).toISOString() });
@@ -2153,25 +2244,6 @@ var presetRoutes = (presets, runtime) => ({
2153
2244
  }
2154
2245
  });
2155
2246
 
2156
- // ../core/dist/validation.js
2157
- var bodyIssues = (context, contentType = "application/json") => {
2158
- const requestBody = context.operation.operation.requestBody;
2159
- if (!requestBody)
2160
- return [];
2161
- const resolved = deref(context.document, requestBody);
2162
- const schema = resolved.content?.[contentType]?.schema;
2163
- if (!schema)
2164
- return [];
2165
- const value = context.body.kind === "json" || context.body.kind === "form" ? context.body.value : void 0;
2166
- if (context.body.kind === "invalid") {
2167
- return [{ path: "", message: `request body is not valid ${context.body.mediaType}` }];
2168
- }
2169
- if (value === void 0) {
2170
- return resolved.required ? [{ path: "", message: "request body is required" }] : [];
2171
- }
2172
- return validateValue(context.document, resolveSchema(context.document, schema), value).map((issue) => ({ path: issue.path.join("."), message: issue.message }));
2173
- };
2174
-
2175
2247
  // src/corpus.ts
2176
2248
  var MEASURE_URI = "http://www.edamam.com/ontologies/edamam.owl#Measure_";
2177
2249
  var RECIPE_URI = "http://www.edamam.com/ontologies/edamam.owl#recipe_";
@@ -2505,7 +2577,7 @@ var DEFAULT_RECIPES = [
2505
2577
  ];
2506
2578
 
2507
2579
  // src/generated/openapi.ts
2508
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Edamam APIs (Mockingbird subset)","description":"Stateful mock subset of Edamam's Food Database v2 (parser, nutrients, image), Nutrition Analysis (nutrition-data, nutrition-details), Recipe Search v2 (search, by-uri, by id), Meal Planner v1 (select) and Shopping List v2, over a built-in corpus. Hand-authored from Edamam's docs and the consumers (edamam-nutrition.adapter.ts, edamam-meal-planning.adapter.ts, makor chat tools/nutrition/client.py).\\n","version":"2","x-mockingbird-upstream":{"note":"Edamam publishes per-API docs, no single OpenAPI; shapes trimmed to the fields our consumers read (zod schemas in edamam-nutrition.adapter.ts and edamam-meal-planning.schemas.ts)."}},"servers":[{"url":"https://api.edamam.com"}],"paths":{"/api/food-database/v2/parser":{"get":{"operationId":"FoodParser","description":"Parse a food description (\`ingr\`) or look up a barcode (\`upc\`): \`parsed\` (the best match with quantity and measure) and \`hints\` (every related food with its measures).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"ingr","in":"query","required":false,"schema":{"type":"string","enum":["2 large eggs","1 cup cooked rice","banana","chicken breast","greek yogurt","3 slices bread","salmon fillet","chicken salad","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"upc","in":"query","required":false,"schema":{"type":"string","enum":["850000000012","850000000029","850000000036","000000000000"]}},{"name":"nutrition-type","in":"query","required":false,"schema":{"type":"string","enum":["cooking","logging"]}},{"name":"categoryLabel","in":"query","required":false,"schema":{"type":"string","enum":["food","meal"]}},{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["generic-foods","packaged-foods","generic-meals"]}},{"name":"health","in":"query","required":false,"schema":{"type":"string","enum":["vegan","vegetarian","gluten-free","dairy-free"]}},{"name":"calories","in":"query","required":false,"schema":{"type":"string","enum":["50-200","300+","100"]}}],"responses":{"200":{"description":"Parser result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParserResponse"}}}},"400":{"description":"Neither ingr nor upc","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"404":{"description":"Unknown UPC","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}}}},"/api/food-database/v2/nutrients":{"post":{"operationId":"FoodNutrients","description":"Full nutrition for foods at given measures.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"Unknown food or measure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutrientsRequest"}}}}}},"/api/food-database/nutrients-from-image":{"post":{"operationId":"FoodFromImage","description":"Recognise a plated meal from an image (beta).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"beta","in":"query","required":false,"schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"Recognition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisionResponse"}}}},"400":{"description":"Not an image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageRequest"}}}}}},"/api/nutrition-data":{"get":{"operationId":"NutritionData","description":"Nutrition analysis of one ingredient line.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"ingr","in":"query","required":true,"schema":{"type":"string","enum":["1 cup cooked rice","2 large eggs","1 banana","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"nutrition-type","in":"query","required":false,"schema":{"type":"string","enum":["cooking","logging"]}}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"Could not parse the ingredient","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}}}},"/api/nutrition-details":{"post":{"operationId":"NutritionDetails","description":"Nutrition analysis of a recipe (ingredient lines).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"No ingredients","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"555":{"description":"Insufficient quality","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeAnalysisRequest"}}}}}},"/api/recipes/v2":{"get":{"operationId":"RecipeSearch","description":"Recipe search v2 (20 per page, \`_links.next\` carries \`_cont\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public","user","any"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string","enum":["chicken","salmon","oats","yogurt","toast","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"health","in":"query","required":false,"schema":{"type":"string","enum":["vegan","vegetarian","gluten-free","dairy-free","pescatarian"]}},{"name":"diet","in":"query","required":false,"schema":{"type":"string","enum":["high-protein","low-carb","high-fiber","balanced"]}},{"name":"mealType","in":"query","required":false,"schema":{"type":"string","enum":["Breakfast","Lunch","Dinner","Snack"]}},{"name":"dishType","in":"query","required":false,"schema":{"type":"string","enum":["Main course","Salad","Cereals","Egg"]}},{"name":"cuisineType","in":"query","required":false,"schema":{"type":"string","enum":["American","Asian","Mediterranean","French"]}},{"name":"calories","in":"query","required":false,"schema":{"type":"string","enum":["100-400","500+","300"]}},{"name":"time","in":"query","required":false,"schema":{"type":"string","enum":["1-10","30","20+"]}},{"name":"excluded","in":"query","required":false,"schema":{"type":"string","enum":["spinach","almonds"]}},{"name":"imageSize","in":"query","required":false,"schema":{"type":"string","enum":["REGULAR","SMALL","THUMBNAIL","LARGE"]}},{"name":"random","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Hits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSearchResponse"}}}},"400":{"description":"Illegal parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/recipes/v2/by-uri":{"get":{"operationId":"RecipesByUri","description":"Recipes by URI (up to 20 \`uri\` parameters).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public"]}},{"name":"uri","in":"query","required":true,"schema":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_baked_salmon","http://www.edamam.com/ontologies/edamam.owl#recipe_missing"]}}],"responses":{"200":{"description":"Hits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSearchResponse"}}}},"400":{"description":"Illegal parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/recipes/v2/{id}":{"get":{"operationId":"RecipeById","description":"One recipe (the \`_links.self\` target).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"id","in":"path","required":true,"schema":{"type":"string","enum":["overnight_oats","veggie_omelette","chicken_rice_bowl","missing"]}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public"]}}],"responses":{"200":{"description":"The recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeHit"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"404":{"description":"Unknown recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/meal-planner/v1/{app_id}/select":{"post":{"operationId":"MealPlanSelect","description":"Select recipes for \`size\` days of a plan (Basic auth plus app_id/app_key).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","enum":["parity"]}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["public"]}}],"responses":{"200":{"description":"Selection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MealPlanResponse"}}}},"400":{"description":"Illegal plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MealPlanRequest"}}}}}},"/api/shopping-list/v2":{"post":{"operationId":"ShoppingList","description":"Aggregate the ingredients of recipes into a shopping list (optionally a cart link).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"shopping-cart","in":"query","required":false,"schema":{"type":"string","enum":["true"]}},{"name":"beta","in":"query","required":false,"schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"Shopping list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShoppingListResponse"}}}},"400":{"description":"Unknown recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShoppingListRequest"}}}}}}},"components":{"parameters":{"AppId":{"name":"app_id","in":"query","required":true,"description":"Edamam application id (any value unless \`apps\` is set). The parity vocabulary is fixed.","schema":{"type":"string","enum":["parity"]}},"AppKey":{"name":"app_key","in":"query","required":true,"schema":{"type":"string","enum":["parity-key"]}}},"schemas":{"Measure":{"type":"object","required":["uri","label","weight"],"properties":{"uri":{"type":"string"},"label":{"type":"string"},"weight":{"type":"number"}}},"Food":{"type":"object","required":["foodId","label","category","categoryLabel"],"properties":{"foodId":{"type":"string"},"label":{"type":"string"},"knownAs":{"type":"string"},"nutrients":{"type":"object","additionalProperties":{"type":"number"}},"brand":{"type":"string"},"category":{"type":"string"},"categoryLabel":{"type":"string"},"foodContentsLabel":{"type":"string"},"image":{"type":"string"},"servingSizes":{"type":"array","items":{"type":"object"}},"servingsPerContainer":{"type":"number"}}},"Recipe":{"type":"object","required":["uri","label","url","yield","ingredientLines","ingredients","totalNutrients","cautions","dietLabels","healthLabels"],"properties":{"uri":{"type":"string"},"label":{"type":"string"},"image":{"type":"string"},"source":{"type":"string"},"url":{"type":"string"},"shareAs":{"type":"string"},"yield":{"type":"number"},"dietLabels":{"type":"array","items":{"type":"string"}},"healthLabels":{"type":"array","items":{"type":"string"}},"cautions":{"type":"array","items":{"type":"string"}},"ingredientLines":{"type":"array","items":{"type":"string"}},"ingredients":{"type":"array","items":{"type":"object","required":["food","quantity"],"properties":{"text":{"type":"string"},"quantity":{"type":"number"},"measure":{"type":["string","null"]},"food":{"type":"string"},"weight":{"type":"number"},"foodCategory":{"type":["string","null"]},"foodId":{"type":"string"},"image":{"type":["string","null"]}}}},"calories":{"type":"number"},"totalWeight":{"type":"number"},"totalTime":{"type":"number"},"cuisineType":{"type":"array","items":{"type":"string"}},"mealType":{"type":"array","items":{"type":"string"}},"dishType":{"type":"array","items":{"type":"string"}},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"totalDaily":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}}}},"Link":{"type":"object","required":["href"],"properties":{"href":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"title":{"type":"string"}}},"ParserResponse":{"type":"object","required":["text","parsed","hints"],"properties":{"text":{"type":"string"},"parsed":{"type":"array","items":{"type":"object","required":["food"],"properties":{"food":{"$ref":"#/components/schemas/Food"},"quantity":{"type":"number"},"measure":{"$ref":"#/components/schemas/Measure"}}}},"hints":{"type":"array","items":{"type":"object","required":["food","measures"],"properties":{"food":{"$ref":"#/components/schemas/Food"},"measures":{"type":"array","items":{"$ref":"#/components/schemas/Measure"}}}}},"_links":{"type":"object"}}},"NutrientsRequest":{"type":"object","required":["ingredients"],"properties":{"ingredients":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["quantity","measureURI","foodId"],"properties":{"quantity":{"type":"number","exclusiveMinimum":0,"maximum":20},"measureURI":{"type":"string","examples":["http://www.edamam.com/ontologies/edamam.owl#Measure_unit","http://www.edamam.com/ontologies/edamam.owl#Measure_cup","http://www.edamam.com/ontologies/edamam.owl#Measure_gram"]},"foodId":{"type":"string","examples":["food_egg","food_banana","food_rice_cooked"]},"qualifiers":{"type":"array","items":{"type":"string"}}}}}}},"NutritionAnalysis":{"type":"object","required":["calories","totalWeight","dietLabels","healthLabels","cautions","totalNutrients","totalDaily"],"properties":{"uri":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"calories":{"type":"number"},"totalWeight":{"type":"number"},"dietLabels":{"type":"array","items":{"type":"string"}},"healthLabels":{"type":"array","items":{"type":"string"}},"cautions":{"type":"array","items":{"type":"string"}},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"totalDaily":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"ingredients":{"type":"array","items":{"type":"object"}},"yield":{"type":"number"}}},"ImageRequest":{"type":"object","required":["image"],"properties":{"image":{"type":"string","minLength":1,"maxLength":200,"examples":["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ==","data:image/png;base64,iVBORw0KGgo="]}}},"VisionResponse":{"type":"object","properties":{"parsed":{"type":"object","properties":{"food":{"$ref":"#/components/schemas/Food"},"quantity":{"type":"number"},"measure":{"$ref":"#/components/schemas/Measure"}}},"recipe":{"type":"object","properties":{"label":{"type":"string"},"calories":{"type":"number"},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}}}}}},"RecipeAnalysisRequest":{"type":"object","required":["ingr"],"properties":{"title":{"type":"string","maxLength":80},"yield":{"type":"integer","minimum":1,"maximum":8},"ingr":{"type":"array","maxItems":4,"items":{"type":"string","minLength":1,"maxLength":60,"examples":["2 large eggs","1 cup cooked rice","1 banana","1 slice bread","xyzzy"]}}}},"RecipeHit":{"type":"object","required":["recipe"],"properties":{"recipe":{"$ref":"#/components/schemas/Recipe"},"_links":{"type":"object","properties":{"self":{"$ref":"#/components/schemas/Link"}}}}},"RecipeSearchResponse":{"type":"object","required":["from","to","count","hits"],"properties":{"from":{"type":"integer"},"to":{"type":"integer"},"count":{"type":"integer"},"_links":{"type":"object","properties":{"next":{"$ref":"#/components/schemas/Link"}}},"hits":{"type":"array","items":{"$ref":"#/components/schemas/RecipeHit"}}}},"MealPlanRequest":{"type":"object","required":["size","plan"],"properties":{"size":{"type":"integer","minimum":1,"maximum":7},"plan":{"$ref":"#/components/schemas/PlanSection"}}},"PlanSection":{"type":"object","properties":{"accept":{"type":"object","required":["all"],"properties":{"all":{"type":"array","maxItems":2,"items":{"type":"object","properties":{"health":{"type":"array","maxItems":2,"items":{"type":"string","enum":["VEGAN","VEGETARIAN","GLUTEN_FREE","DAIRY_FREE","PESCATARIAN"]}},"meal":{"type":"array","maxItems":2,"items":{"type":"string","enum":["breakfast","lunch/dinner","snack"]}},"dish":{"type":"array","maxItems":2,"items":{"type":"string","enum":["main course","salad","cereals","egg"]}}}}}}},"fit":{"type":"object","properties":{"ENERC_KCAL":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"PROCNT":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"CHOCDF":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"FAT":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"FIBTG":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}}}},"exclude":{"type":"array","maxItems":2,"items":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_baked_salmon"]}},"sections":{"type":"object","maxProperties":4,"additionalProperties":{"$ref":"#/components/schemas/PlanSection"},"examples":[{"Breakfast":{},"Lunch":{},"Dinner":{}}]}}},"MealPlanResponse":{"type":"object","properties":{"status":{"type":"string","enum":["OK","INCOMPLETE","TIME_OUT"]},"selection":{"type":"array","items":{"type":"object","required":["sections"],"properties":{"sections":{"type":"object","additionalProperties":{"type":"object","properties":{"assigned":{"type":"string"},"_links":{"type":"object","properties":{"self":{"$ref":"#/components/schemas/Link"}}},"sections":{"type":"object"}}}}}}}}},"ShoppingListRequest":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["quantity","item"],"properties":{"quantity":{"type":"number","exclusiveMinimum":0,"maximum":10},"measure":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#Measure_serving"]},"item":{"type":"string","examples":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_chicken_rice_bowl","http://www.edamam.com/ontologies/edamam.owl#recipe_missing"]}}}}}},"ShoppingListResponse":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"type":"object","required":["foodId","food","quantities"],"properties":{"foodId":{"type":"string"},"food":{"type":"string"},"quantities":{"type":"array","items":{"type":"object","required":["quantity","measure"],"properties":{"quantity":{"type":"number"},"measure":{"type":"string"},"qualifiers":{"type":"array","items":{"type":"string"}}}}}}}},"_links":{"type":"object","properties":{"shopping-cart":{"$ref":"#/components/schemas/Link"}}}}},"FoodError":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","enum":["error"]},"message":{"type":"string"},"error":{"type":"string"}}},"RecipeErrors":{"type":"array","items":{"type":"object","required":["errorCode","message"],"properties":{"errorCode":{"type":"string"},"message":{"type":"string"},"params":{"type":"array","items":{"type":"string"}}}}}}}}`);
2580
+ var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Edamam APIs (Mockingbird subset)","description":"Stateful mock subset of Edamam's Food Database v2 (parser, nutrients, image), Nutrition Analysis (nutrition-data, nutrition-details), Recipe Search v2 (search, by-uri, by id), Meal Planner v1 (select) and Shopping List v2, over a built-in corpus. Hand-authored from Edamam's docs and the consumers (edamam-nutrition.adapter.ts, edamam-meal-planning.adapter.ts, the Python chat client's tools/nutrition/client.py).\\n","version":"2","x-mockingbird-upstream":{"note":"Edamam publishes per-API docs, no single OpenAPI; shapes trimmed to the fields our consumers read (zod schemas in edamam-nutrition.adapter.ts and edamam-meal-planning.schemas.ts)."}},"servers":[{"url":"https://api.edamam.com"}],"paths":{"/api/food-database/v2/parser":{"get":{"operationId":"FoodParser","description":"Parse a food description (\`ingr\`) or look up a barcode (\`upc\`): \`parsed\` (the best match with quantity and measure) and \`hints\` (every related food with its measures).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"ingr","in":"query","required":false,"schema":{"type":"string","enum":["2 large eggs","1 cup cooked rice","banana","chicken breast","greek yogurt","3 slices bread","salmon fillet","chicken salad","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"upc","in":"query","required":false,"schema":{"type":"string","enum":["850000000012","850000000029","850000000036","000000000000"]}},{"name":"nutrition-type","in":"query","required":false,"schema":{"type":"string","enum":["cooking","logging"]}},{"name":"categoryLabel","in":"query","required":false,"schema":{"type":"string","enum":["food","meal"]}},{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["generic-foods","packaged-foods","generic-meals"]}},{"name":"health","in":"query","required":false,"schema":{"type":"string","enum":["vegan","vegetarian","gluten-free","dairy-free"]}},{"name":"calories","in":"query","required":false,"schema":{"type":"string","enum":["50-200","300+","100"]}}],"responses":{"200":{"description":"Parser result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParserResponse"}}}},"400":{"description":"Neither ingr nor upc","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"404":{"description":"Unknown UPC","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}}}},"/api/food-database/v2/nutrients":{"post":{"operationId":"FoodNutrients","description":"Full nutrition for foods at given measures.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"Unknown food or measure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutrientsRequest"}}}}}},"/api/food-database/nutrients-from-image":{"post":{"operationId":"FoodFromImage","description":"Recognise a plated meal from an image (beta).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"beta","in":"query","required":false,"schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"Recognition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VisionResponse"}}}},"400":{"description":"Not an image","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImageRequest"}}}}}},"/api/nutrition-data":{"get":{"operationId":"NutritionData","description":"Nutrition analysis of one ingredient line.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"ingr","in":"query","required":true,"schema":{"type":"string","enum":["1 cup cooked rice","2 large eggs","1 banana","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"nutrition-type","in":"query","required":false,"schema":{"type":"string","enum":["cooking","logging"]}}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"Could not parse the ingredient","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}}}},"/api/nutrition-details":{"post":{"operationId":"NutritionDetails","description":"Nutrition analysis of a recipe (ingredient lines).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"}],"responses":{"200":{"description":"Nutrition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NutritionAnalysis"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"422":{"description":"No ingredients","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}},"555":{"description":"Insufficient quality","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FoodError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeAnalysisRequest"}}}}}},"/api/recipes/v2":{"get":{"operationId":"RecipeSearch","description":"Recipe search v2 (20 per page, \`_links.next\` carries \`_cont\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public","user","any"]}},{"name":"q","in":"query","required":false,"schema":{"type":"string","enum":["chicken","salmon","oats","yogurt","toast","xyzzy"]},"description":"Parity vocabulary; the mock parses any text."},{"name":"health","in":"query","required":false,"schema":{"type":"string","enum":["vegan","vegetarian","gluten-free","dairy-free","pescatarian"]}},{"name":"diet","in":"query","required":false,"schema":{"type":"string","enum":["high-protein","low-carb","high-fiber","balanced"]}},{"name":"mealType","in":"query","required":false,"schema":{"type":"string","enum":["Breakfast","Lunch","Dinner","Snack"]}},{"name":"dishType","in":"query","required":false,"schema":{"type":"string","enum":["Main course","Salad","Cereals","Egg"]}},{"name":"cuisineType","in":"query","required":false,"schema":{"type":"string","enum":["American","Asian","Mediterranean","French"]}},{"name":"calories","in":"query","required":false,"schema":{"type":"string","enum":["100-400","500+","300"]}},{"name":"time","in":"query","required":false,"schema":{"type":"string","enum":["1-10","30","20+"]}},{"name":"excluded","in":"query","required":false,"schema":{"type":"string","enum":["spinach","almonds"]}},{"name":"imageSize","in":"query","required":false,"schema":{"type":"string","enum":["REGULAR","SMALL","THUMBNAIL","LARGE"]}},{"name":"random","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Hits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSearchResponse"}}}},"400":{"description":"Illegal parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/recipes/v2/by-uri":{"get":{"operationId":"RecipesByUri","description":"Recipes by URI (up to 20 \`uri\` parameters).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public"]}},{"name":"uri","in":"query","required":true,"schema":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_baked_salmon","http://www.edamam.com/ontologies/edamam.owl#recipe_missing"]}}],"responses":{"200":{"description":"Hits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeSearchResponse"}}}},"400":{"description":"Illegal parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/recipes/v2/{id}":{"get":{"operationId":"RecipeById","description":"One recipe (the \`_links.self\` target).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"id","in":"path","required":true,"schema":{"type":"string","enum":["overnight_oats","veggie_omelette","chicken_rice_bowl","missing"]}},{"name":"type","in":"query","required":true,"schema":{"type":"string","enum":["public"]}}],"responses":{"200":{"description":"The recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeHit"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"404":{"description":"Unknown recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}}}},"/api/meal-planner/v1/{app_id}/select":{"post":{"operationId":"MealPlanSelect","description":"Select recipes for \`size\` days of a plan (Basic auth plus app_id/app_key).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"app_id","in":"path","required":true,"schema":{"type":"string","enum":["parity"]}},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["public"]}}],"responses":{"200":{"description":"Selection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MealPlanResponse"}}}},"400":{"description":"Illegal plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MealPlanRequest"}}}}}},"/api/shopping-list/v2":{"post":{"operationId":"ShoppingList","description":"Aggregate the ingredients of recipes into a shopping list (optionally a cart link).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/AppId"},{"$ref":"#/components/parameters/AppKey"},{"name":"Edamam-Account-User","in":"header","required":false,"schema":{"type":"string","pattern":"^[a-f0-9]{1,30}$","description":"The first 30 hex chars of sha256(user id), as our meal adapter sends it."}},{"name":"shopping-cart","in":"query","required":false,"schema":{"type":"string","enum":["true"]}},{"name":"beta","in":"query","required":false,"schema":{"type":"string","enum":["true"]}}],"responses":{"200":{"description":"Shopping list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShoppingListResponse"}}}},"400":{"description":"Unknown recipe","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"401":{"description":"Unknown app_id / app_key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}},"429":{"description":"Usage limits are exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeErrors"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShoppingListRequest"}}}}}}},"components":{"parameters":{"AppId":{"name":"app_id","in":"query","required":true,"description":"Edamam application id (any value unless \`apps\` is set). The parity vocabulary is fixed.","schema":{"type":"string","enum":["parity"]}},"AppKey":{"name":"app_key","in":"query","required":true,"schema":{"type":"string","enum":["parity-key"]}}},"schemas":{"Measure":{"type":"object","required":["uri","label","weight"],"properties":{"uri":{"type":"string"},"label":{"type":"string"},"weight":{"type":"number"}}},"Food":{"type":"object","required":["foodId","label","category","categoryLabel"],"properties":{"foodId":{"type":"string"},"label":{"type":"string"},"knownAs":{"type":"string"},"nutrients":{"type":"object","additionalProperties":{"type":"number"}},"brand":{"type":"string"},"category":{"type":"string"},"categoryLabel":{"type":"string"},"foodContentsLabel":{"type":"string"},"image":{"type":"string"},"servingSizes":{"type":"array","items":{"type":"object"}},"servingsPerContainer":{"type":"number"}}},"Recipe":{"type":"object","required":["uri","label","url","yield","ingredientLines","ingredients","totalNutrients","cautions","dietLabels","healthLabels"],"properties":{"uri":{"type":"string"},"label":{"type":"string"},"image":{"type":"string"},"source":{"type":"string"},"url":{"type":"string"},"shareAs":{"type":"string"},"yield":{"type":"number"},"dietLabels":{"type":"array","items":{"type":"string"}},"healthLabels":{"type":"array","items":{"type":"string"}},"cautions":{"type":"array","items":{"type":"string"}},"ingredientLines":{"type":"array","items":{"type":"string"}},"ingredients":{"type":"array","items":{"type":"object","required":["food","quantity"],"properties":{"text":{"type":"string"},"quantity":{"type":"number"},"measure":{"type":["string","null"]},"food":{"type":"string"},"weight":{"type":"number"},"foodCategory":{"type":["string","null"]},"foodId":{"type":"string"},"image":{"type":["string","null"]}}}},"calories":{"type":"number"},"totalWeight":{"type":"number"},"totalTime":{"type":"number"},"cuisineType":{"type":"array","items":{"type":"string"}},"mealType":{"type":"array","items":{"type":"string"}},"dishType":{"type":"array","items":{"type":"string"}},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"totalDaily":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}}}},"Link":{"type":"object","required":["href"],"properties":{"href":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"title":{"type":"string"}}},"ParserResponse":{"type":"object","required":["text","parsed","hints"],"properties":{"text":{"type":"string"},"parsed":{"type":"array","items":{"type":"object","required":["food"],"properties":{"food":{"$ref":"#/components/schemas/Food"},"quantity":{"type":"number"},"measure":{"$ref":"#/components/schemas/Measure"}}}},"hints":{"type":"array","items":{"type":"object","required":["food","measures"],"properties":{"food":{"$ref":"#/components/schemas/Food"},"measures":{"type":"array","items":{"$ref":"#/components/schemas/Measure"}}}}},"_links":{"type":"object"}}},"NutrientsRequest":{"type":"object","required":["ingredients"],"properties":{"ingredients":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["quantity","measureURI","foodId"],"properties":{"quantity":{"type":"number","exclusiveMinimum":0,"maximum":20},"measureURI":{"type":"string","examples":["http://www.edamam.com/ontologies/edamam.owl#Measure_unit","http://www.edamam.com/ontologies/edamam.owl#Measure_cup","http://www.edamam.com/ontologies/edamam.owl#Measure_gram"]},"foodId":{"type":"string","examples":["food_egg","food_banana","food_rice_cooked"]},"qualifiers":{"type":"array","items":{"type":"string"}}}}}}},"NutritionAnalysis":{"type":"object","required":["calories","totalWeight","dietLabels","healthLabels","cautions","totalNutrients","totalDaily"],"properties":{"uri":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"calories":{"type":"number"},"totalWeight":{"type":"number"},"dietLabels":{"type":"array","items":{"type":"string"}},"healthLabels":{"type":"array","items":{"type":"string"}},"cautions":{"type":"array","items":{"type":"string"}},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"totalDaily":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}},"ingredients":{"type":"array","items":{"type":"object"}},"yield":{"type":"number"}}},"ImageRequest":{"type":"object","required":["image"],"properties":{"image":{"type":"string","minLength":1,"maxLength":200,"examples":["data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ==","data:image/png;base64,iVBORw0KGgo="]}}},"VisionResponse":{"type":"object","properties":{"parsed":{"type":"object","properties":{"food":{"$ref":"#/components/schemas/Food"},"quantity":{"type":"number"},"measure":{"$ref":"#/components/schemas/Measure"}}},"recipe":{"type":"object","properties":{"label":{"type":"string"},"calories":{"type":"number"},"totalNutrients":{"type":"object","additionalProperties":{"type":"object","required":["label","quantity","unit"],"properties":{"label":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}}}}}},"RecipeAnalysisRequest":{"type":"object","required":["ingr"],"properties":{"title":{"type":"string","maxLength":80},"yield":{"type":"integer","minimum":1,"maximum":8},"ingr":{"type":"array","maxItems":4,"items":{"type":"string","minLength":1,"maxLength":60,"examples":["2 large eggs","1 cup cooked rice","1 banana","1 slice bread","xyzzy"]}}}},"RecipeHit":{"type":"object","required":["recipe"],"properties":{"recipe":{"$ref":"#/components/schemas/Recipe"},"_links":{"type":"object","properties":{"self":{"$ref":"#/components/schemas/Link"}}}}},"RecipeSearchResponse":{"type":"object","required":["from","to","count","hits"],"properties":{"from":{"type":"integer"},"to":{"type":"integer"},"count":{"type":"integer"},"_links":{"type":"object","properties":{"next":{"$ref":"#/components/schemas/Link"}}},"hits":{"type":"array","items":{"$ref":"#/components/schemas/RecipeHit"}}}},"MealPlanRequest":{"type":"object","required":["size","plan"],"properties":{"size":{"type":"integer","minimum":1,"maximum":7},"plan":{"$ref":"#/components/schemas/PlanSection"}}},"PlanSection":{"type":"object","properties":{"accept":{"type":"object","required":["all"],"properties":{"all":{"type":"array","maxItems":2,"items":{"type":"object","properties":{"health":{"type":"array","maxItems":2,"items":{"type":"string","enum":["VEGAN","VEGETARIAN","GLUTEN_FREE","DAIRY_FREE","PESCATARIAN"]}},"meal":{"type":"array","maxItems":2,"items":{"type":"string","enum":["breakfast","lunch/dinner","snack"]}},"dish":{"type":"array","maxItems":2,"items":{"type":"string","enum":["main course","salad","cereals","egg"]}}}}}}},"fit":{"type":"object","properties":{"ENERC_KCAL":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"PROCNT":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"CHOCDF":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"FAT":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}},"FIBTG":{"type":"object","properties":{"min":{"type":"number","minimum":0},"max":{"type":"number","minimum":0},"mark":{"type":"number","minimum":0}}}}},"exclude":{"type":"array","maxItems":2,"items":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_baked_salmon"]}},"sections":{"type":"object","maxProperties":4,"additionalProperties":{"$ref":"#/components/schemas/PlanSection"},"examples":[{"Breakfast":{},"Lunch":{},"Dinner":{}}]}}},"MealPlanResponse":{"type":"object","properties":{"status":{"type":"string","enum":["OK","INCOMPLETE","TIME_OUT"]},"selection":{"type":"array","items":{"type":"object","required":["sections"],"properties":{"sections":{"type":"object","additionalProperties":{"type":"object","properties":{"assigned":{"type":"string"},"_links":{"type":"object","properties":{"self":{"$ref":"#/components/schemas/Link"}}},"sections":{"type":"object"}}}}}}}}},"ShoppingListRequest":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["quantity","item"],"properties":{"quantity":{"type":"number","exclusiveMinimum":0,"maximum":10},"measure":{"type":"string","enum":["http://www.edamam.com/ontologies/edamam.owl#Measure_serving"]},"item":{"type":"string","examples":["http://www.edamam.com/ontologies/edamam.owl#recipe_overnight_oats","http://www.edamam.com/ontologies/edamam.owl#recipe_chicken_rice_bowl","http://www.edamam.com/ontologies/edamam.owl#recipe_missing"]}}}}}},"ShoppingListResponse":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"type":"object","required":["foodId","food","quantities"],"properties":{"foodId":{"type":"string"},"food":{"type":"string"},"quantities":{"type":"array","items":{"type":"object","required":["quantity","measure"],"properties":{"quantity":{"type":"number"},"measure":{"type":"string"},"qualifiers":{"type":"array","items":{"type":"string"}}}}}}}},"_links":{"type":"object","properties":{"shopping-cart":{"$ref":"#/components/schemas/Link"}}}}},"FoodError":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","enum":["error"]},"message":{"type":"string"},"error":{"type":"string"}}},"RecipeErrors":{"type":"array","items":{"type":"object","required":["errorCode","message"],"properties":{"errorCode":{"type":"string"},"message":{"type":"string"},"params":{"type":"array","items":{"type":"string"}}}}}}}}`);
2509
2581
  var operationIds = ["FoodParser", "FoodNutrients", "FoodFromImage", "NutritionData", "NutritionDetails", "RecipeSearch", "RecipesByUri", "RecipeById", "MealPlanSelect", "ShoppingList"];
2510
2582
  var supportedOperationIds = ["FoodParser", "FoodNutrients", "FoodFromImage", "NutritionData", "NutritionDetails", "RecipeSearch", "RecipesByUri", "RecipeById", "MealPlanSelect", "ShoppingList"];
2511
2583
 
@@ -2839,7 +2911,7 @@ var EDAMAM_PRESETS = {
2839
2911
  rules: everywhere(429, "usage_limits", "Usage limits are exceeded")
2840
2912
  },
2841
2913
  payment_required: {
2842
- description: "Every API answers 402 (plan quota exhausted; makor chat treats it as a rate limit)",
2914
+ description: "Every API answers 402 (plan quota exhausted; the Python chat client treats it as a rate limit)",
2843
2915
  rules: everywhere(402, "payment_required", "Payment required: plan limits reached")
2844
2916
  },
2845
2917
  unauthorized: {
@@ -3482,4 +3554,4 @@ export {
3482
3554
  appIdCredential,
3483
3555
  EdamamAPI
3484
3556
  };
3485
- //# sourceMappingURL=chunk-DWHV3RH2.js.map
3557
+ //# sourceMappingURL=chunk-CWYJE4IF.js.map