# Custom script examples (general)

## Introduction

This page includes scripts that may be useful either in and of themselves or as a starting point for your own scripting work:

* [C#](#c)
* [Go](#go)
* [JavaScript](#javascript)
* [PHP](#php)
* [Python](#python)
* [Rust](#rust)

## C\#

This script takes an XML input string, loads it into an `XmlDocument`, and then converts that XML to a JSON string using the `Newtonsoft.Json` library.

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> Converting XML to JSON</summary>

{% code lineNumbers="true" %}

```csharp
using System;
using System.Xml;
using Newtonsoft.Json;
public class FunctionHandler
{
    public string Handle(string input)
    {
        var xmlDoc = new XmlDocument();
        
        // Set up XML namespace manager
        var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
        namespaceManager.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
        namespaceManager.AddNamespace("", "http://api.jdplc.com/schemas/mc/pim/feproducts");
        
        // Load the XML string into XmlDocument
        xmlDoc.LoadXml(input);

        // Convert the XML to JSON
        string jsonText = JsonConvert.SerializeXmlNode(xmlDoc, Newtonsoft.Json.Formatting.Indented, true);

        return jsonText;
    }
}
```

{% endcode %}

</details>

## Go

The script below demonstrates the handling of variables, logging, updating response codes, and modifying payloads.

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> Helper functions demo</summary>

{% code lineNumbers="true" %}

```go
package function

import (
	"encoding/json"
	"fmt"
)

type Variables map[string]string

func (v *Variables) UnmarshalJSON(bytes []byte) error {
	if string(bytes) == "[]" {
		*v = make(Variables)
	} else {
		d := make(map[string]string)
		err := json.Unmarshal(bytes, &d)
		if err != nil {
			return err
		}

		*v = d
	}

	return nil
}

type Request struct {
	Payload   string    `json:"payload"`
	Variables Variables `json:"variables,omitempty"`
	Flow      Flow      `json:"flow,omitempty"`

	ResponseCode int      `json:"response_code,omitempty"`
	Message      string   `json:"message,omitempty"`
	Logs         []string `json:"logs,omitempty"`
}

func (r *Request) GetVariable(name string) (string, bool) {
	if r.Variables == nil {
		return "", false
	}

	value, ok := r.Variables[name]
	return value, ok
}

func (r *Request) AddLog(log string) {
	r.Logs = append(r.Logs, log)
}

func (r *Request) DeserializePayload() (interface{}, error) {
	var payload interface{}
	err := json.Unmarshal([]byte(r.Payload), &payload)
	if err != nil {
		return nil, err
	}

	return payload, nil
}

type Flow struct {
	Variables Variables `json:"variables"`
}

func (f *Flow) GetVariable(name string) (string, bool) {
	if f.Variables == nil {
		return "", false
	}

	value, ok := f.Variables[name]
	return value, ok
}

// Handle
// handles a serverless request
// example payload:
//
//	{
//	  "payload": "",
//	  "meta": null,
//	  "variables": [],
//	  "flow": {
//	    "variables": []
//	  },
//	  "response_code": 0,
//	  "message": "",
//	  "logs": []
//	}
func Handle(request []byte) (string, error) {
	r := &Request{}
	err := json.Unmarshal(request, r)
	if err != nil {
		return "", fmt.Errorf("failed to deserialize request: %s", err)
	}

	if r.Variables != nil && len(r.Variables) > 0 {
		for k, v := range r.Variables {
			r.AddLog(fmt.Sprintf("variable: %s=%s", k, v))
		}
	} else {
		r.AddLog("no variables")
	}

	if r.Flow.Variables != nil && len(r.Flow.Variables) > 0 {
		for k, v := range r.Flow.Variables {
			r.AddLog(fmt.Sprintf("flow variable: %s=%s", k, v))
		}
	} else {
		r.AddLog("no flow variables")
	}

	r.ResponseCode = 12
	r.AddLog("updated response code")

	r.Variables["key1"] = "value1"
	r.Variables["key2"] = "value2"
	r.AddLog("added 2 variables (key1, key2)")

	var p interface{}
	if p, err = r.DeserializePayload(); err != nil {
		return "", fmt.Errorf("failed to deserialize payload: %s", err)
	}

	var ok bool
	var m map[string]interface{}
	if m, ok = p.(map[string]interface{}); !ok {
		r.Payload = "not a json object"
	} else {
		m["key1"] = "value1"
		m["key2"] = "value2"

		var result []byte
		result, err = json.Marshal(m)
		if err != nil {
			return "", fmt.Errorf("failed to serialize payload: %s", err)
		}

		r.Payload = string(result)
	}

	d, err := json.Marshal(r)
	if err != nil {
		return "", err
	}

	return string(d), nil
}
```

{% endcode %}

</details>

## JavaScript

The script below demonstrates how to convert a JSON file to XML, using JavaScript.

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> JSON to XML</summary>

{% code lineNumbers="true" %}

```javascript

/**
  * @param data
  * @param {string} data.payload the payload as a string|null
  * @param {Object.<string, any>} data.variables any variables as key/value
  * @param {Object.<string, any>} data.meta any meta as key/value
  */

  
module.exports = async function (data) {
 var obj = JSON.parse(data.payload)
function jsonToXml(jsonObj, rootName = 'root') {
  let xml = '';

  const buildXml = (obj, parentName) => {
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        if (typeof value === 'object') {
          xml += `<${key}>`;
          buildXml(value, key);
          xml += `</${key}>`;
        } else {
          xml += `<${key}>${value}</${key}>`;
        }
      }
    }
  };

  xml += `<?xml version="1.0" encoding="UTF-8"?>`;
  xml += `<${rootName}>`;
  buildXml(jsonObj, rootName);
  xml += `</${rootName}>`;

  return xml;
}


const xmlData = jsonToXml(obj, 'data');

const jsonString = JSON.stringify(xmlData);
  return {"payload":jsonString};
};
```

{% endcode %}

</details>

## PHP

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> Converting JSON to CSV</summary>

{% code lineNumbers="true" %}

```php
<?php
function handle($data) {
  // Decode the JSON string into an array
  $dataArray = json_decode($data['payload'], true);
  if ($dataArray === null) {
    // JSON decoding failed
    return 'Failed to decode JSON.';
  }
  // Create an empty array to store the CSV rows
  $csvRows = [];
  // Add the CSV headers
  if (!empty($dataArray)) {
    $headers = array_keys($dataArray[0]);
    $csvRows[] = $headers;
  }
  // Add the CSV rows
  foreach ($dataArray as $row) {
    $csvRows[] = array_values($row);
  }
  // Generate the CSV string
  $csvString = '';
  foreach ($csvRows as $row) {
    $csvString .= implode(',', $row) . "\n";
  }
  // Return the CSV string or error message
  return ['payload' => !empty($csvString) ? $csvString : 'Failed to convert JSON to CSV'];
}
```

{% endcode %}

</details>

## Python

The script below demonstrates how to convert a CSV file to JSON, using Python.

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> CSV to JSON</summary>

{% code lineNumbers="true" %}

```python
# class Request:
#     """
#     The class representing the request.

#     Attributes
#     ----------
#     payload : str
#         the payload as a string or null
#     variables : object
#         any variables as key/value
#     meta : object
#         any meta as key/value
#     """

def handle(req):
    import json, io, csv
    data = json.loads(req)
    reader = csv.DictReader(io.StringIO(data['payload']), delimiter=';')
    json_data = json.dumps(list(reader))
    return json.dumps({'payload':json_data})
```

{% endcode %}

</details>

## Rust

The script below demonstrates how helper functions can be used, with the `handle()` function acting as the main driver.

<details>

<summary><img src="https://2440044887-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLYNcUBVQwSkOMG6KjZfz%2Fuploads%2FoTQs7eEfsfqZUPfRoiT7%2Ficon_code.svg?alt=media&#x26;token=b4a6f8e5-8ebf-4ef8-ab24-a130d29b50c5" alt="" data-size="line"> Helper functions demo</summary>

{% code lineNumbers="true" %}

```rust
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::GenericError;
use serde::de::{Deserializer, Error as SerdeError, Unexpected};

/// Custom deserializer that converts `[]` into `{}` (empty HashMap)
fn deserialize_variables<'de, D>(deserializer: D) -> Result<HashMap<String, String>, D::Error>
where
    D: Deserializer<'de>,
{
    match Value::deserialize(deserializer)? {
        Value::Object(map) => {
            let mut hashmap = HashMap::new();
            for (key, value) in map {
                if let Value::String(s) = value {
                    hashmap.insert(key, s);
                } else {
                    return Err(D::Error::custom(format!(
                        "Expected string values in HashMap, found: {:?}",
                        value
                    )));
                }
            }
            Ok(hashmap)
        }
        Value::Array(arr) if arr.is_empty() => Ok(HashMap::new()), // Convert `[]` to `{}` 
        other => Err(D::Error::invalid_type(
            Unexpected::Other(&format!("{:?}", other)),
            &"a map of strings or an empty array",
        )),
    }
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RequestBody {
    pub payload: Option<String>,
    pub meta: Vec<Value>,

    #[serde(default, deserialize_with = "deserialize_variables")]
    pub variables: HashMap<String, String>,

    pub flow: FlowData,

    #[serde(default)] // Default value if missing
    pub response_code: i32,

    #[serde(default)] // Default empty string if missing
    pub message: String,

    #[serde(default)] // Default empty list if missing
    pub logs: Vec<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FlowData {
    #[serde(default, deserialize_with = "deserialize_variables")]
    pub variables: HashMap<String, String>,
}



#[allow(dead_code)]
impl RequestBody {
    /// Creates a new `RequestBody` with default values
    pub fn new() -> Self {
        Self {
            payload: None,
            meta: Vec::new(),
            variables: HashMap::new(),
            flow: FlowData {
                variables: HashMap::new(),
            },
            response_code: 0,
            message: String::new(),
            logs: Vec::new(),
        }
    }

    /// Create a `RequestBody` from `Bytes`
    pub fn from_bytes(body_bytes: Bytes) -> Self {
        let body_str = String::from_utf8_lossy(&body_bytes);
        println!("Received body: {}", body_str); // Debugging output
    
        match serde_json::from_slice(&body_bytes) {
            Ok(body) => body,
            Err(err) => {
                println!("Deserialization error: {}", err); // Print exact deserialization error
                let mut default_body = Self::new();
                default_body.add_log("Failed to deserialize RequestBody; using default.");
                default_body
            }
        }
    }
    

    /// Add a log entry
    pub fn add_log(&mut self, message: &str) {
        self.logs.push(message.to_string());
    }

    /// Get the payload as a `String`
    pub fn get_payload(&self) -> Option<&String> {
        self.payload.as_ref()
    }

    /// Set a new payload
    pub fn set_payload(&mut self, payload: &str) {
        self.payload = Some(payload.to_string());
        self.add_log(&format!("Payload set to: {}", payload));
    }

    /// Deserializes the payload into a generic struct if the payload is present and valid JSON
    pub fn deserialize_payload<T: for<'de> serde::Deserialize<'de>>(&self) -> Option<T> {
        match &self.payload {
            Some(payload) if !payload.trim().is_empty() => {
                match serde_json::from_str::<T>(payload) {
                    Ok(deserialized) => Some(deserialized),
                    Err(err) => {
                        eprintln!("Failed to deserialize payload: {}", err);
                        None
                    }
                }
            }
            _ => {
                // Log explicitly that the payload was empty or null
                println!("Skipping deserialization: payload is null or empty.");
                None
            }
        }
    }   
    

    /// Serialize and update the payload from a given struct
    pub fn update_payload<T: serde::Serialize>(&mut self, new_payload: &T) {
        match serde_json::to_string(new_payload) {
            Ok(serialized) => {
                self.payload = Some(serialized);
                self.add_log("Payload updated successfully.");
            }
            Err(err) => {
                eprintln!("Failed to serialize new payload: {}", err);
                self.add_log("Failed to update payload.");
            }
        }
    }

    /// Iterate over variables
    pub fn variables_iter(&self) -> impl Iterator<Item = (&String, &String)> {
        self.variables.iter()
    }

    /// Add a variable
    pub fn add_variable(&mut self, key: &str, value: &str) {
        self.variables.insert(key.to_string(), value.to_string());
        self.add_log(&format!("Variable added: {} = {}", key, value));
    }

    /// Get a variable by key
    pub fn get_variable(&self, key: &str) -> Option<&String> {
        self.variables.get(key)
    }

    /// Add a flow variable
    pub fn add_flow_variable(&mut self, key: &str, value: &str) {
        self.flow.variables.insert(key.to_string(), value.to_string());
        self.add_log(&format!("Flow variable added: {} = {}", key, value));
    }

    /// Get a flow variable by key
    pub fn get_flow_variable(&self, key: &str) -> Option<&String> {
        self.flow.variables.get(key)
    }

    /// Clear all logs
    pub fn clear_logs(&mut self) {
        self.logs.clear();
        self.add_log("Logs cleared.");
    }

    /// Reset all variables
    pub fn reset_variables(&mut self) {
        self.variables.clear();
        self.add_log("All variables cleared.");
    }
}

#[derive(Deserialize, Serialize, Debug)]
struct CustomPayload {
    test: bool,
    name: Option<String>,
}

#[allow(unused_mut)]
pub fn handle(body_bytes: Bytes) -> Result<Bytes, GenericError> {
    // Deserialize bytes into the `RequestBody` struct
    let mut body = RequestBody::from_bytes(body_bytes);

    // Check if payload exists and is non-empty before deserializing
    if let Some(payload) = &body.payload {
        if !payload.trim().is_empty() {
            // Attempt to deserialize the payload into `CustomPayload`
            if let Some(mut custom_payload) = body.deserialize_payload::<CustomPayload>() {
                body.add_log(&format!("Deserialized payload: {:?}", custom_payload));

                // Modify the payload
                custom_payload.test = !custom_payload.test;
                custom_payload.name = Some("Updated Name".to_string());

                // Serialize the updated payload back to JSON
                body.update_payload(&custom_payload);
            } else {
                body.add_log("Failed to deserialize payload.");
            }
        } else {
            body.add_log("Payload is empty, skipping deserialization.");
        }
    } else {
        body.add_log("Payload is null, skipping deserialization.");
    }

    // Add and retrieve variables
    body.add_variable("Key1", "Value1");
    body.add_variable("Key2", "Value2");

    // Collect variables into a temporary Vec to avoid borrowing conflicts
    let variables: Vec<(String, String)> = body
        .variables_iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();

    for (key, value) in variables {
        body.add_log(&format!("Iterated variable: {} = {}", key, value));
    }
    
    // Serialize the updated body back to JSON and return
    let response_body = serde_json::to_vec(&body).unwrap();
    Ok(Bytes::from(response_body))
}
```

{% endcode %}

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.wearepatchworks.com/product-documentation/developer-hub/custom-scripting/custom-script-examples-general.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
