Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ build/

# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc

# Locally Stored API Models (generated by codegen.sh)
.models/
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
# amz_sp_api

AmzSpApi - Unofficial Ruby gem for the Amazon Selling Partner API (SP-API)
AmzSpApi Unofficial Ruby gem for the Amazon Selling Partner API (SPAPI).

This SDK is automatically generated by running [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) on each model from https://github.com/amzn/selling-partner-api-models using the [codegen.sh](codegen.sh) script.
This SDK is mechanically generated from the upstream Amazon Selling Partner API OpenAPI models
(https://github.com/amzn/selling-partner-api-models). Generation is deterministic and pinned to a
specific upstream commit SHA.

Generation flow:

The regeneration pipeline is explicit, ordered, and shell-safe. All scripts are written for Bash
and run with strict mode enabled.

The recommended entrypoint is `run.sh`, which orchestrates the full pipeline end-to-end:

Script responsibilities:
- `pull_models.sh` snapshots the upstream `models/` directory at a specific commit.
- `codegen.sh` runs Swagger Codegen against that snapshot and writes generated output to `lib/`.
- `hoist.sh` normalizes shared runtime files and injects provenance headers.
- `release.sh` commits the generated artifacts and tags the release as
`amzn/selling-partner-api-models/<short_sha>`, linking back to the exact upstream models commit.

All generated files include provenance comments pointing back to the exact upstream models commit.
Hand‑maintained files are intentionally kept separate from generated output.

Auto-generated documentation is nested here: This is a handy way to see all the API model class names and corresponding files you need to require for them, e.g. require 'finances-api-model' to use https://www.rubydoc.info/gems/amz_sp_api/AmzSpApi/FinancesApiModel/DefaultApi

For authoritative API behavior and business rules, always refer to the official Amazon SP‑API documentation.

but https://developer-docs.amazon.com/sp-api is more comprehensive.

## Installation
Expand Down
107 changes: 92 additions & 15 deletions codegen.sh
Original file line number Diff line number Diff line change
@@ -1,22 +1,99 @@
#!/bin/bash
set -euo pipefail

# exit on error
set -e
# Generate Ruby client code from pinned Amazon SP-API models.
# Environment + prerequisites are enforced by env.sh.

for FILE in `find ../selling-partner-api-models/models -name "*.json"`; do
API_NAME=`echo $FILE | awk -F/ '{print $4}'`
MODULE_NAME=`echo $API_NAME | perl -pe 's/(^|-)./uc($&)/ge;s/-//g'`
if [[ ! -f "./env.sh" ]]; then
echo "Missing ./env.sh. Run from repo root." >&2
exit 1
fi
# shellcheck disable=SC1091
source "./env.sh"

rm -r lib/${API_NAME}
mkdir lib/$API_NAME
cp config.json lib/$API_NAME
sed -i '' "s/GEMNAME/${API_NAME}/g" lib/${API_NAME}/config.json
sed -i '' "s/MODULENAME/${MODULE_NAME}/g" lib/${API_NAME}/config.json
# Contract inputs (exported by env.sh)
: "${MODELS_DIR:?MODELS_DIR must be set by env.sh}"
: "${UPSTREAM_SHA:?UPSTREAM_SHA must be set by env.sh}"
: "${MODELS_URL:?MODELS_URL must be set by env.sh}"
: "${CODEGEN_ARTIFACT_FILE:?CODEGEN_ARTIFACT_FILE must be set by env.sh}"
: "${RUNTIME_SOURCE_DIR:?RUNTIME_SOURCE_DIR must be set by env.sh}"
FORCE="${FORCE:-0}"

swagger-codegen generate -i $FILE -l ruby -c lib/${API_NAME}/config.json -o lib/$API_NAME
# Guard: avoid regenerating when lib/ already matches the current upstream SHA,
# unless FORCE=1 is explicitly set.
if [[ -f "$CODEGEN_ARTIFACT_FILE" && "$FORCE" != "1" ]]; then
existing_sha="$(cat "$CODEGEN_ARTIFACT_FILE" 2>/dev/null || true)"
if [[ "$existing_sha" == "$UPSTREAM_SHA" ]]; then
echo "lib/ already generated from ${MODELS_URL}; refusing to re-run codegen without FORCE=1" >&2
echo "Run: FORCE=1 ./codegen.sh to regenerate anyway" >&2
exit 1
fi
fi

mv lib/${API_NAME}/lib/${API_NAME}.rb lib/
mv lib/${API_NAME}/lib/${API_NAME}/* lib/${API_NAME}
rm -r lib/${API_NAME}/lib
rm lib/${API_NAME}/*.gemspec
# Start clean so deletions propagate, but preserve a couple hand-maintained files.
KEEP_FILES=("amz_sp_api.rb" "amz_sp_api_version.rb")

KEEP_TMP_DIR="$(mktemp -d)"
restore_keep_files() {
# Always attempt to restore preserved files on exit (success or failure).
# This prevents a partial/failed codegen run from leaving lib/ missing hand-maintained files.
mkdir -p lib
for f in "${KEEP_FILES[@]}"; do
if [[ -f "$KEEP_TMP_DIR/$f" ]]; then
cp "$KEEP_TMP_DIR/$f" "lib/$f"
fi
done
}

trap 'restore_keep_files; rm -rf "$KEEP_TMP_DIR"' EXIT

for f in "${KEEP_FILES[@]}"; do
if [[ -f "lib/$f" ]]; then
cp "lib/$f" "$KEEP_TMP_DIR/$f"
fi
done

rm -rf lib
mkdir -p lib

# Generate code for each API spec
find "$MODELS_DIR" -name "*.json" -print0 | while IFS= read -r -d '' FILE; do
FILE_PATH="${FILE#$MODELS_DIR/}"
API_NAME="${FILE_PATH%%/*}"

# Amazon Seller Central still uses Fulfillment Inbound API v0.
# The models repo contains both v0 and v1; keep them distinct.
if [[ "$API_NAME" == "fulfillment-inbound-api-model" && "$FILE" == *V0.json ]]; then
API_NAME="${API_NAME}-V0"
fi

MODULE_NAME="$(echo "$API_NAME" | perl -pe 's/(^|-)./uc($&)/ge;s/-//g')"

rm -rf "lib/${API_NAME}"
mkdir -p "lib/${API_NAME}"
cp config.json "lib/${API_NAME}/config.json"

gsed -i "s/GEMNAME/${API_NAME}/g" "lib/${API_NAME}/config.json"
gsed -i "s/MODULENAME/${MODULE_NAME}/g" "lib/${API_NAME}/config.json"

swagger-codegen generate \
-i "$FILE" \
-l ruby \
-c "lib/${API_NAME}/config.json" \
-o "lib/${API_NAME}"

mv "lib/${API_NAME}/lib/${API_NAME}.rb" lib/
mv "lib/${API_NAME}/lib/${API_NAME}/"* "lib/${API_NAME}"
rm -rf "lib/${API_NAME}/lib"
rm -f "lib/${API_NAME}/"*.gemspec
done

# Restore preserved files (if they existed before cleanup)
restore_keep_files

# Note: post-generation normalization (hoisting + provenance headers)
# is handled separately by hoist.sh.

# Record provenance so subsequent runs can detect an identical upstream SHA
mkdir -p "$(dirname "$CODEGEN_ARTIFACT_FILE")"
echo "$UPSTREAM_SHA" > "$CODEGEN_ARTIFACT_FILE"
76 changes: 76 additions & 0 deletions env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash
set -euo pipefail

# Repo root anchor: allow scripts to be run from any working directory.
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
export ROOT_DIR

require_cmd() {
local cmd="$1"
local hint="${2:-}"
command -v "$cmd" >/dev/null 2>&1 || {
echo "Missing required command: ${cmd}" >&2
if [[ -n "$hint" ]]; then
echo "$hint" >&2
fi
exit 1
}
}

require_file() {
local path="$1"
local hint="${2:-}"
[[ -f "$path" ]] || {
echo "Missing required file: ${path}" >&2
if [[ -n "$hint" ]]; then
echo "$hint" >&2
fi
exit 1
}
}

# Tool prerequisites used by codegen.sh
require_cmd gsed "Install with: brew install gnu-sed"
require_cmd swagger-codegen
require_cmd perl

# Contract inputs: models checkout metadata (produced by ./pull_models.sh)
MODELS_ENV_FILE=".models/.env"
export MODELS_ENV_FILE

if [[ -f "$MODELS_ENV_FILE" ]]; then
# shellcheck disable=SC1091
source "$MODELS_ENV_FILE"
else
echo "Missing ${MODELS_ENV_FILE}." >&2
echo "Run ./pull_models.sh first." >&2
exit 1
fi

: "${MODELS_DIR:?MODELS_DIR must be set}"
: "${UPSTREAM_SHA:?UPSTREAM_SHA must be set}"

require_file "config.json" "Expected in repo root; required for swagger-codegen config template"

# Contract outputs derived from UPSTREAM_SHA / repo structure
MODELS_URL="https://github.com/amzn/selling-partner-api-models/tree/${UPSTREAM_SHA}/models"
export MODELS_URL

CODEGEN_ARTIFACT_FILE="lib/.codegen_models_sha"
export CODEGEN_ARTIFACT_FILE

# Canonical runtime source module for hoisted shared runtime files
RUNTIME_SOURCE_DIR="lib/fulfillment-outbound-api-model"
export RUNTIME_SOURCE_DIR

if [[ ! -d "$MODELS_DIR" ]]; then
echo "MODELS_DIR is not a directory: '$MODELS_DIR'" >&2
exit 1
fi

# Contract outputs expected by scripts sourcing env.sh
export MODELS_DIR
export UPSTREAM_SHA
FORCE="${FORCE:-0}"
export FORCE
100 changes: 100 additions & 0 deletions hoist.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/bin/bash
set -euo pipefail

# Post-generation normalization:
# - hoist shared runtime into top-level lib/
# - patch hoisted files to remove hardcoded namespaces
# - add provenance headers to generated Ruby files
# - write CODEGEN_ARTIFACT_FILE for deterministic skipping

if [[ ! -f "./env.sh" ]]; then
echo "Missing ./env.sh. Create it (or restore it) to provide MODELS_DIR/UPSTREAM_SHA and prerequisites." >&2
exit 1
fi
# shellcheck disable=SC1091
source "./env.sh"

: "${UPSTREAM_SHA:?UPSTREAM_SHA must be set by env.sh}"
: "${MODELS_URL:?MODELS_URL must be set by env.sh}"
: "${CODEGEN_ARTIFACT_FILE:?CODEGEN_ARTIFACT_FILE must be set by env.sh}"
: "${RUNTIME_SOURCE_DIR:?RUNTIME_SOURCE_DIR must be set by env.sh}"

if [[ ! -d "lib" ]]; then
echo "Missing lib/ directory. Run ./codegen.sh first." >&2
exit 1
fi

#
# Hoist shared runtime files into top-level lib/ from the canonical runtime source module.
if [[ ! -d "$RUNTIME_SOURCE_DIR" ]]; then
echo "Warning: runtime source dir not found: ${RUNTIME_SOURCE_DIR}" >&2
else
COMMON_FILES=("api_client.rb" "api_error.rb" "configuration.rb")
for name in "${COMMON_FILES[@]}"; do
src="${RUNTIME_SOURCE_DIR}/${name}"
dest="lib/$name"
if [[ -f "$src" ]]; then
{
echo "# NOTE: Generated and hoisted to lib/ by hoist.sh"
echo "# Source: ${src}"
echo
cat "$src"
} > "$dest"

# Normalize module namespace for hoisted files.
#
# These files (api_client.rb, api_error.rb, configuration.rb) are hoisted
# into top-level lib/ so they act as shared runtime infrastructure across
# all generated SP-API modules.
#
# The upstream swagger generator hardcodes an API module namespace
# (e.g. AmzSpApi::AmazonWarehousingAndDistributionModel) into these files.
# Once hoisted, that namespace is incorrect and must be removed.
#
# Additionally, api_client.rb may reference concrete model namespaces when
# deserializing return types. We rewrite those lookups to dynamically scan
# AmzSpApi submodules and resolve the correct model class at runtime.

# Remove any hardcoded nested API module namespace in hoisted runtime files
gsed -i -E '/^module AmzSpApi::[A-Za-z0-9_]+$/d' "$dest"

# Rewrite any hardcoded namespace-based return_type resolver to a dynamic resolver
gsed -i -E 's/AmzSpApi::[A-Za-z0-9_]+\.const_get\(return_type\)\.build_from_hash\(data\)/AmzSpApi.constants.map{|c| AmzSpApi.const_get(c)}.select{|sub| sub.kind_of?(Module)}.detect{|sub| sub.const_defined?(return_type)}.const_get(return_type).build_from_hash(data)/g' "$dest"

# Add inline comments at patched sites (inline-only; no new lines).
gsed -i 's/^\(module AmzSpApi\)\s*$/\1 # NOTE: patched by hoist.sh – hoisted runtime file, removed nested API namespace/' "$dest"
gsed -i 's/\(AmzSpApi.constants.map{|c| AmzSpApi.const_get(c)}.select{|sub| sub.kind_of?(Module)}.detect{|sub| sub.const_defined?(return_type)}.const_get(return_type).build_from_hash(data)\)/\1 # NOTE: patched by hoist.sh – resolve return_type across AmzSpApi submodules/' "$dest"
else
echo "Warning: ${name} not found in ${RUNTIME_SOURCE_DIR}" >&2
fi
done
fi

# Add a short provenance note to the top of generated Ruby files.
# This is intentionally lightweight and avoids editing hand-maintained files.
PROVENANCE_HEADER="# NOTE: Generated from ${MODELS_URL}\n# NOTE: If you need to regenerate: ./pull_models.sh && ./codegen.sh\n\n"
while IFS= read -r -d '' rb; do
# Skip hand-maintained files
if [[ "$rb" == "lib/amz_sp_api.rb" || "$rb" == "lib/amz_sp_api_version.rb" ]]; then
continue
fi

# Skip only the top-level hoisted runtime files (they already carry a note).
# Module-scoped runtime files (e.g. lib/<api>/api_client.rb) must still receive provenance headers.
if [[ "$rb" == "lib/api_client.rb" || "$rb" == "lib/api_error.rb" || "$rb" == "lib/configuration.rb" ]]; then
continue
fi

# Avoid double-prepending if run manually
if head -n 1 "$rb" | grep -q "^# NOTE: Generated from https://github.com/amzn/selling-partner-api-models/tree/"; then
continue
fi

tmp="${rb}.tmp"
printf "%b" "$PROVENANCE_HEADER" > "$tmp"
cat "$rb" >> "$tmp"
mv "$tmp" "$rb"
done < <(find lib -type f -name "*.rb" -print0)

# Record provenance so we can skip re-running codegen for the same upstream SHA
echo "$UPSTREAM_SHA" > "$CODEGEN_ARTIFACT_FILE"
1 change: 1 addition & 0 deletions lib/.codegen_models_sha
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
df0f6a4d062dc27a42952c07a681d937172e38d8
Loading