flowmarkup:
  title: Order Processing via External API
  version: 1
  description: Authenticate, create order, upload receipt, handle errors. Demonstrates all request action patterns.
  requires:
    REQUEST: ["api.example.com"]
    SECRET: [client_secret]

  input:
    api_base_url:
      $kind: URL
      _notes_: "Base URL for the API (e.g. https://api.example.com)"
    client_id:
      $kind: STRING
      _notes_: OAuth client ID
    order_data:
      $kind: MAP
      $type: application/json
      _notes_: "Order payload with customer_id, items, and idempotency_key"
    receipt_pdf:
      $kind: BINARY
      _notes_: Receipt PDF to attach to the order

  output:
    order_id: STRING
    order_status: STRING
  examples:
    new_order:
      _notes_: Create a new order and attach a PDF receipt
      input:
        api_base_url: https://api.example.com
        client_id: my-client-id
        # client_secret provided via SECRET.client_secret (not in examples)
        order_data:
          customer_id: cust-001
          idempotency_key: idem-abc123
          items:
          - sku: WG-1001
            quantity: 2
            price: 49.99
        receipt_pdf: "<binary PDF data>"
      output:
        order_id: ord-7890
        order_status: confirmed
    idempotent_replay:
      _notes_: Same idempotency key returns the existing order
      input:
        api_base_url: https://api.example.com
        client_id: my-client-id
        # client_secret provided via SECRET.client_secret (not in examples)
        order_data:
          customer_id: cust-001
          idempotency_key: idem-abc123
          items:
          - sku: WG-1001
            quantity: 2
            price: 49.99
        receipt_pdf: "<binary PDF data>"
      output:
        order_id: ord-7890
        order_status: confirmed

  throws:
  - $kind: ApiAuthError
    $parent: AuthenticationError
    _notes_: Failed to authenticate with the API
    data:
      status:
        $kind: INTEGER
        _notes_: HTTP status code returned
      response:
        $kind: MAP
        $type: application/json
        _notes_: Raw authentication error response
  - $kind: OrderCreationError
    _notes_: API rejected the order
    data:
      status:
        $kind: INTEGER
        _notes_: HTTP status code returned

  vars:
    auth_token:
      $exportable: false
    order_id:
    order_status:

  do:
    # ── SSRF Prevention ──────────────────────────────────────────────
    # The REQUEST allowlist (requires.REQUEST: ["api.example.com"]) is the
    # primary defense: the engine MUST reject any resolved host not on the
    # list.  In addition, follow these safe-construction patterns:
    #
    #   SAFE:   Static base URL + literal path segments
    #           url: "=api_base_url + '/v2/orders'"
    #           (api_base_url is an input validated as $kind: URL and the
    #            appended path is a string literal — no user-controlled
    #            host/scheme injection is possible.)
    #
    #   UNSAFE: Entirely user-supplied URL without allowlist
    #           url: =user_provided_url          # NEVER do this
    #
    #   UNSAFE: String interpolation that could smuggle host changes
    #           url: "=base + '/' + user_path"   # user_path could be
    #                                             # "../../other-host"
    #
    # When path segments come from variables, validate them with a CEL
    # pattern guard:
    #   assert:
    #     condition: =order_id.matches('^[a-zA-Z0-9_-]+$')
    #     message: Invalid order_id
    # ──────────────────────────────────────────────────────────────────

    # 1. Authenticate — POST urlencoded form to get a bearer token
  - request:
      _label_: Get API auth token
      method: POST
      # SSRF-safe: api_base_url validated as URL input; path is a literal.
      url: "=api_base_url + '/oauth/token'"
      auth:
        basic:
          username: =client_id
          password: =SECRET.client_secret
      body:
        urlencoded:
          grant_type: client_credentials
      timeout: 10s
      parseAs: JSON
      retry: 3/2s/EXPONENTIAL
      result:
        auth_status: =RESULT.status
        auth_response: =RESULT.body
      errors:
      - ConnectionError
      - TimeoutError

    # 2. Check auth response — status mapped above, so no auto-throw on 4xx
  - throw:
      condition: =auth_status != 200
      error: ApiAuthError
      message: "Auth failed with status {{auth_status}}"
      data:
        status: =auth_status
        response: =auth_response

  - set:
      auth_token: =auth_response.access_token

    # 3. Create order — POST JSON body with bearer auth and idempotency key
  - try:
      do:
      - request:
          _label_: Create order via API
          method: POST
          # SSRF-safe: static base + literal path; no user-controlled segments.
          url: "=api_base_url + '/v2/orders'"
          auth:
            bearer: =auth_token
          headers:
            X-Idempotency-Key: =order_data.idempotency_key
            Accept: application/json
          body:
            json:
              customer_id: =order_data.customer_id
              items: =order_data.items
              currency: USD
              metadata:
                source: flowmarkup
                version: 1
          expect:
            contentType: application/json
          timeout: 30s
          parseAs: JSON
          retry:
            maxAttempts: 3
            delay: 1s
            backoff: EXPONENTIAL
            maxDelay: 15s
          result:
            order_response: =RESULT.body
          errors:
          - ConnectionError
          - HttpError
          - TimeoutError
      catch:
        HttpError:
        - logError: "Server error creating order: {{ERROR.DATA.status}}"
        - throw:
            error: OrderCreationError
            message: "Server error: {{ERROR.DATA.status}}"
            data:
              status: =ERROR.DATA.status

  - set:
      order_id: =order_response.id
      order_status: =order_response.status

    # 4. Upload receipt PDF — multipart with binary data
  - request:
      _label_: Attach receipt PDF to order
      method: POST
      # SSRF-safe: order_id is set from a trusted API response (line 167),
      # not directly from user input.  If order_id could be user-controlled,
      # validate with: =order_id.matches('^[a-zA-Z0-9_-]+$')
      url: "=api_base_url + '/v2/orders/' + order_id + '/attachments'"
      auth:
        bearer: =auth_token
      body:
        multipart:
        - name: file
          filename: "receipt-{{order_id}}.pdf"
          contentType: application/pdf
          data: =receipt_pdf
        - name: type
          data: receipt
        - name: description
          data: "Order receipt for {{order_id}}"
      timeout: 2m
      result:
        upload_status: =RESULT.status
      errors:
      - ConnectionError
      - TimeoutError

  - logWarn: "Receipt upload returned {{upload_status}}, continuing"
    condition: =upload_status != 200 && upload_status != 201

  - return: [order_id, order_status]
