This is page 55 of 59. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .ci
│ ├── continuous.release.cloudbuild.yaml
│ ├── generate_release_table.sh
│ ├── integration.cloudbuild.yaml
│ ├── quickstart_test
│ │ ├── go.integration.cloudbuild.yaml
│ │ ├── js.integration.cloudbuild.yaml
│ │ ├── py.integration.cloudbuild.yaml
│ │ ├── run_go_tests.sh
│ │ ├── run_js_tests.sh
│ │ ├── run_py_tests.sh
│ │ └── setup_hotels_sample.sql
│ ├── test_with_coverage.sh
│ └── versioned.release.cloudbuild.yaml
├── .github
│ ├── auto-label.yaml
│ ├── blunderbuss.yml
│ ├── CODEOWNERS
│ ├── header-checker-lint.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feature_request.yml
│ │ └── question.yml
│ ├── label-sync.yml
│ ├── labels.yaml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── release-please.yml
│ ├── renovate.json5
│ ├── sync-repo-settings.yaml
│ └── workflows
│ ├── cloud_build_failure_reporter.yml
│ ├── deploy_dev_docs.yaml
│ ├── deploy_previous_version_docs.yaml
│ ├── deploy_versioned_docs.yaml
│ ├── docs_deploy.yaml
│ ├── docs_preview_clean.yaml
│ ├── docs_preview_deploy.yaml
│ ├── lint.yaml
│ ├── schedule_reporter.yml
│ ├── sync-labels.yaml
│ └── tests.yaml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── .hugo
│ ├── archetypes
│ │ └── default.md
│ ├── assets
│ │ ├── icons
│ │ │ └── logo.svg
│ │ └── scss
│ │ ├── _styles_project.scss
│ │ └── _variables_project.scss
│ ├── go.mod
│ ├── go.sum
│ ├── hugo.toml
│ ├── layouts
│ │ ├── _default
│ │ │ └── home.releases.releases
│ │ ├── index.llms-full.txt
│ │ ├── index.llms.txt
│ │ ├── partials
│ │ │ ├── hooks
│ │ │ │ └── head-end.html
│ │ │ ├── navbar-version-selector.html
│ │ │ ├── page-meta-links.html
│ │ │ └── td
│ │ │ └── render-heading.html
│ │ ├── robot.txt
│ │ └── shortcodes
│ │ ├── include.html
│ │ ├── ipynb.html
│ │ └── regionInclude.html
│ ├── package-lock.json
│ ├── package.json
│ └── static
│ ├── favicons
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ └── favicon.ico
│ └── js
│ └── w3.js
├── CHANGELOG.md
├── cmd
│ ├── options_test.go
│ ├── options.go
│ ├── root_test.go
│ ├── root.go
│ └── version.txt
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOPER.md
├── Dockerfile
├── docs
│ └── en
│ ├── _index.md
│ ├── about
│ │ ├── _index.md
│ │ └── faq.md
│ ├── concepts
│ │ ├── _index.md
│ │ └── telemetry
│ │ ├── index.md
│ │ ├── telemetry_flow.png
│ │ └── telemetry_traces.png
│ ├── getting-started
│ │ ├── _index.md
│ │ ├── colab_quickstart.ipynb
│ │ ├── configure.md
│ │ ├── introduction
│ │ │ ├── _index.md
│ │ │ └── architecture.png
│ │ ├── local_quickstart_go.md
│ │ ├── local_quickstart_js.md
│ │ ├── local_quickstart.md
│ │ ├── mcp_quickstart
│ │ │ ├── _index.md
│ │ │ ├── inspector_tools.png
│ │ │ └── inspector.png
│ │ └── quickstart
│ │ ├── go
│ │ │ ├── adkgo
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── genAI
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── genkit
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── langchain
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ ├── openAI
│ │ │ │ ├── go.mod
│ │ │ │ ├── go.sum
│ │ │ │ └── quickstart.go
│ │ │ └── quickstart_test.go
│ │ ├── golden.txt
│ │ ├── js
│ │ │ ├── genAI
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── genkit
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── langchain
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ ├── llamaindex
│ │ │ │ ├── package-lock.json
│ │ │ │ ├── package.json
│ │ │ │ └── quickstart.js
│ │ │ └── quickstart.test.js
│ │ ├── python
│ │ │ ├── __init__.py
│ │ │ ├── adk
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── core
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── langchain
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ ├── llamaindex
│ │ │ │ ├── quickstart.py
│ │ │ │ └── requirements.txt
│ │ │ └── quickstart_test.py
│ │ └── shared
│ │ ├── cloud_setup.md
│ │ ├── configure_toolbox.md
│ │ └── database_setup.md
│ ├── how-to
│ │ ├── _index.md
│ │ ├── connect_via_geminicli.md
│ │ ├── connect_via_mcp.md
│ │ ├── connect-ide
│ │ │ ├── _index.md
│ │ │ ├── alloydb_pg_admin_mcp.md
│ │ │ ├── alloydb_pg_mcp.md
│ │ │ ├── bigquery_mcp.md
│ │ │ ├── cloud_sql_mssql_admin_mcp.md
│ │ │ ├── cloud_sql_mssql_mcp.md
│ │ │ ├── cloud_sql_mysql_admin_mcp.md
│ │ │ ├── cloud_sql_mysql_mcp.md
│ │ │ ├── cloud_sql_pg_admin_mcp.md
│ │ │ ├── cloud_sql_pg_mcp.md
│ │ │ ├── firestore_mcp.md
│ │ │ ├── looker_mcp.md
│ │ │ ├── mssql_mcp.md
│ │ │ ├── mysql_mcp.md
│ │ │ ├── neo4j_mcp.md
│ │ │ ├── postgres_mcp.md
│ │ │ ├── spanner_mcp.md
│ │ │ └── sqlite_mcp.md
│ │ ├── deploy_docker.md
│ │ ├── deploy_gke.md
│ │ ├── deploy_toolbox.md
│ │ ├── export_telemetry.md
│ │ └── toolbox-ui
│ │ ├── edit-headers.gif
│ │ ├── edit-headers.png
│ │ ├── index.md
│ │ ├── optional-param-checked.png
│ │ ├── optional-param-unchecked.png
│ │ ├── run-tool.gif
│ │ ├── tools.png
│ │ └── toolsets.png
│ ├── reference
│ │ ├── _index.md
│ │ ├── cli.md
│ │ └── prebuilt-tools.md
│ ├── resources
│ │ ├── _index.md
│ │ ├── authServices
│ │ │ ├── _index.md
│ │ │ └── google.md
│ │ ├── sources
│ │ │ ├── _index.md
│ │ │ ├── alloydb-admin.md
│ │ │ ├── alloydb-pg.md
│ │ │ ├── bigquery.md
│ │ │ ├── bigtable.md
│ │ │ ├── cassandra.md
│ │ │ ├── clickhouse.md
│ │ │ ├── cloud-healthcare.md
│ │ │ ├── cloud-monitoring.md
│ │ │ ├── cloud-sql-admin.md
│ │ │ ├── cloud-sql-mssql.md
│ │ │ ├── cloud-sql-mysql.md
│ │ │ ├── cloud-sql-pg.md
│ │ │ ├── couchbase.md
│ │ │ ├── dataplex.md
│ │ │ ├── dgraph.md
│ │ │ ├── elasticsearch.md
│ │ │ ├── firebird.md
│ │ │ ├── firestore.md
│ │ │ ├── http.md
│ │ │ ├── looker.md
│ │ │ ├── mindsdb.md
│ │ │ ├── mongodb.md
│ │ │ ├── mssql.md
│ │ │ ├── mysql.md
│ │ │ ├── neo4j.md
│ │ │ ├── oceanbase.md
│ │ │ ├── oracle.md
│ │ │ ├── postgres.md
│ │ │ ├── redis.md
│ │ │ ├── serverless-spark.md
│ │ │ ├── singlestore.md
│ │ │ ├── spanner.md
│ │ │ ├── sqlite.md
│ │ │ ├── tidb.md
│ │ │ ├── trino.md
│ │ │ ├── valkey.md
│ │ │ └── yugabytedb.md
│ │ └── tools
│ │ ├── _index.md
│ │ ├── alloydb
│ │ │ ├── _index.md
│ │ │ ├── alloydb-create-cluster.md
│ │ │ ├── alloydb-create-instance.md
│ │ │ ├── alloydb-create-user.md
│ │ │ ├── alloydb-get-cluster.md
│ │ │ ├── alloydb-get-instance.md
│ │ │ ├── alloydb-get-user.md
│ │ │ ├── alloydb-list-clusters.md
│ │ │ ├── alloydb-list-instances.md
│ │ │ ├── alloydb-list-users.md
│ │ │ └── alloydb-wait-for-operation.md
│ │ ├── alloydbainl
│ │ │ ├── _index.md
│ │ │ └── alloydb-ai-nl.md
│ │ ├── bigquery
│ │ │ ├── _index.md
│ │ │ ├── bigquery-analyze-contribution.md
│ │ │ ├── bigquery-conversational-analytics.md
│ │ │ ├── bigquery-execute-sql.md
│ │ │ ├── bigquery-forecast.md
│ │ │ ├── bigquery-get-dataset-info.md
│ │ │ ├── bigquery-get-table-info.md
│ │ │ ├── bigquery-list-dataset-ids.md
│ │ │ ├── bigquery-list-table-ids.md
│ │ │ ├── bigquery-search-catalog.md
│ │ │ └── bigquery-sql.md
│ │ ├── bigtable
│ │ │ ├── _index.md
│ │ │ └── bigtable-sql.md
│ │ ├── cassandra
│ │ │ ├── _index.md
│ │ │ └── cassandra-cql.md
│ │ ├── clickhouse
│ │ │ ├── _index.md
│ │ │ ├── clickhouse-execute-sql.md
│ │ │ ├── clickhouse-list-databases.md
│ │ │ ├── clickhouse-list-tables.md
│ │ │ └── clickhouse-sql.md
│ │ ├── cloudhealthcare
│ │ │ ├── _index.md
│ │ │ ├── cloud-healthcare-fhir-fetch-page.md
│ │ │ ├── cloud-healthcare-fhir-patient-everything.md
│ │ │ ├── cloud-healthcare-fhir-patient-search.md
│ │ │ ├── cloud-healthcare-get-dataset.md
│ │ │ ├── cloud-healthcare-get-dicom-store-metrics.md
│ │ │ ├── cloud-healthcare-get-dicom-store.md
│ │ │ ├── cloud-healthcare-get-fhir-resource.md
│ │ │ ├── cloud-healthcare-get-fhir-store-metrics.md
│ │ │ ├── cloud-healthcare-get-fhir-store.md
│ │ │ ├── cloud-healthcare-list-dicom-stores.md
│ │ │ ├── cloud-healthcare-list-fhir-stores.md
│ │ │ ├── cloud-healthcare-retrieve-rendered-dicom-instance.md
│ │ │ ├── cloud-healthcare-search-dicom-instances.md
│ │ │ ├── cloud-healthcare-search-dicom-series.md
│ │ │ └── cloud-healthcare-search-dicom-studies.md
│ │ ├── cloudmonitoring
│ │ │ ├── _index.md
│ │ │ └── cloud-monitoring-query-prometheus.md
│ │ ├── cloudsql
│ │ │ ├── _index.md
│ │ │ ├── cloudsqlcreatedatabase.md
│ │ │ ├── cloudsqlcreateusers.md
│ │ │ ├── cloudsqlgetinstances.md
│ │ │ ├── cloudsqllistdatabases.md
│ │ │ ├── cloudsqllistinstances.md
│ │ │ ├── cloudsqlmssqlcreateinstance.md
│ │ │ ├── cloudsqlmysqlcreateinstance.md
│ │ │ ├── cloudsqlpgcreateinstances.md
│ │ │ └── cloudsqlwaitforoperation.md
│ │ ├── couchbase
│ │ │ ├── _index.md
│ │ │ └── couchbase-sql.md
│ │ ├── dataform
│ │ │ ├── _index.md
│ │ │ └── dataform-compile-local.md
│ │ ├── dataplex
│ │ │ ├── _index.md
│ │ │ ├── dataplex-lookup-entry.md
│ │ │ ├── dataplex-search-aspect-types.md
│ │ │ └── dataplex-search-entries.md
│ │ ├── dgraph
│ │ │ ├── _index.md
│ │ │ └── dgraph-dql.md
│ │ ├── elasticsearch
│ │ │ ├── _index.md
│ │ │ └── elasticsearch-esql.md
│ │ ├── firebird
│ │ │ ├── _index.md
│ │ │ ├── firebird-execute-sql.md
│ │ │ └── firebird-sql.md
│ │ ├── firestore
│ │ │ ├── _index.md
│ │ │ ├── firestore-add-documents.md
│ │ │ ├── firestore-delete-documents.md
│ │ │ ├── firestore-get-documents.md
│ │ │ ├── firestore-get-rules.md
│ │ │ ├── firestore-list-collections.md
│ │ │ ├── firestore-query-collection.md
│ │ │ ├── firestore-query.md
│ │ │ ├── firestore-update-document.md
│ │ │ └── firestore-validate-rules.md
│ │ ├── http
│ │ │ ├── _index.md
│ │ │ └── http.md
│ │ ├── looker
│ │ │ ├── _index.md
│ │ │ ├── looker-add-dashboard-element.md
│ │ │ ├── looker-conversational-analytics.md
│ │ │ ├── looker-create-project-file.md
│ │ │ ├── looker-delete-project-file.md
│ │ │ ├── looker-dev-mode.md
│ │ │ ├── looker-get-connection-databases.md
│ │ │ ├── looker-get-connection-schemas.md
│ │ │ ├── looker-get-connection-table-columns.md
│ │ │ ├── looker-get-connection-tables.md
│ │ │ ├── looker-get-connections.md
│ │ │ ├── looker-get-dashboards.md
│ │ │ ├── looker-get-dimensions.md
│ │ │ ├── looker-get-explores.md
│ │ │ ├── looker-get-filters.md
│ │ │ ├── looker-get-looks.md
│ │ │ ├── looker-get-measures.md
│ │ │ ├── looker-get-models.md
│ │ │ ├── looker-get-parameters.md
│ │ │ ├── looker-get-project-file.md
│ │ │ ├── looker-get-project-files.md
│ │ │ ├── looker-get-projects.md
│ │ │ ├── looker-health-analyze.md
│ │ │ ├── looker-health-pulse.md
│ │ │ ├── looker-health-vacuum.md
│ │ │ ├── looker-make-dashboard.md
│ │ │ ├── looker-make-look.md
│ │ │ ├── looker-query-sql.md
│ │ │ ├── looker-query-url.md
│ │ │ ├── looker-query.md
│ │ │ ├── looker-run-dashboard.md
│ │ │ ├── looker-run-look.md
│ │ │ └── looker-update-project-file.md
│ │ ├── mindsdb
│ │ │ ├── _index.md
│ │ │ ├── mindsdb-execute-sql.md
│ │ │ └── mindsdb-sql.md
│ │ ├── mongodb
│ │ │ ├── _index.md
│ │ │ ├── mongodb-aggregate.md
│ │ │ ├── mongodb-delete-many.md
│ │ │ ├── mongodb-delete-one.md
│ │ │ ├── mongodb-find-one.md
│ │ │ ├── mongodb-find.md
│ │ │ ├── mongodb-insert-many.md
│ │ │ ├── mongodb-insert-one.md
│ │ │ ├── mongodb-update-many.md
│ │ │ └── mongodb-update-one.md
│ │ ├── mssql
│ │ │ ├── _index.md
│ │ │ ├── mssql-execute-sql.md
│ │ │ ├── mssql-list-tables.md
│ │ │ └── mssql-sql.md
│ │ ├── mysql
│ │ │ ├── _index.md
│ │ │ ├── mysql-execute-sql.md
│ │ │ ├── mysql-list-active-queries.md
│ │ │ ├── mysql-list-table-fragmentation.md
│ │ │ ├── mysql-list-tables-missing-unique-indexes.md
│ │ │ ├── mysql-list-tables.md
│ │ │ └── mysql-sql.md
│ │ ├── neo4j
│ │ │ ├── _index.md
│ │ │ ├── neo4j-cypher.md
│ │ │ ├── neo4j-execute-cypher.md
│ │ │ └── neo4j-schema.md
│ │ ├── oceanbase
│ │ │ ├── _index.md
│ │ │ ├── oceanbase-execute-sql.md
│ │ │ └── oceanbase-sql.md
│ │ ├── oracle
│ │ │ ├── _index.md
│ │ │ ├── oracle-execute-sql.md
│ │ │ └── oracle-sql.md
│ │ ├── postgres
│ │ │ ├── _index.md
│ │ │ ├── postgres-execute-sql.md
│ │ │ ├── postgres-list-active-queries.md
│ │ │ ├── postgres-list-available-extensions.md
│ │ │ ├── postgres-list-installed-extensions.md
│ │ │ ├── postgres-list-schemas.md
│ │ │ ├── postgres-list-tables.md
│ │ │ ├── postgres-list-views.md
│ │ │ └── postgres-sql.md
│ │ ├── redis
│ │ │ ├── _index.md
│ │ │ └── redis.md
│ │ ├── serverless-spark
│ │ │ ├── _index.md
│ │ │ ├── serverless-spark-cancel-batch.md
│ │ │ ├── serverless-spark-get-batch.md
│ │ │ └── serverless-spark-list-batches.md
│ │ ├── singlestore
│ │ │ ├── _index.md
│ │ │ ├── singlestore-execute-sql.md
│ │ │ └── singlestore-sql.md
│ │ ├── spanner
│ │ │ ├── _index.md
│ │ │ ├── spanner-execute-sql.md
│ │ │ ├── spanner-list-tables.md
│ │ │ └── spanner-sql.md
│ │ ├── sqlite
│ │ │ ├── _index.md
│ │ │ ├── sqlite-execute-sql.md
│ │ │ └── sqlite-sql.md
│ │ ├── tidb
│ │ │ ├── _index.md
│ │ │ ├── tidb-execute-sql.md
│ │ │ └── tidb-sql.md
│ │ ├── trino
│ │ │ ├── _index.md
│ │ │ ├── trino-execute-sql.md
│ │ │ └── trino-sql.md
│ │ ├── utility
│ │ │ ├── _index.md
│ │ │ └── wait.md
│ │ ├── valkey
│ │ │ ├── _index.md
│ │ │ └── valkey.md
│ │ └── yuagbytedb
│ │ ├── _index.md
│ │ └── yugabytedb-sql.md
│ ├── samples
│ │ ├── _index.md
│ │ ├── alloydb
│ │ │ ├── _index.md
│ │ │ ├── ai-nl
│ │ │ │ ├── alloydb_ai_nl.ipynb
│ │ │ │ └── index.md
│ │ │ └── mcp_quickstart.md
│ │ ├── bigquery
│ │ │ ├── _index.md
│ │ │ ├── colab_quickstart_bigquery.ipynb
│ │ │ ├── local_quickstart.md
│ │ │ └── mcp_quickstart
│ │ │ ├── _index.md
│ │ │ ├── inspector_tools.png
│ │ │ └── inspector.png
│ │ └── looker
│ │ ├── _index.md
│ │ ├── looker_gemini_oauth
│ │ │ ├── _index.md
│ │ │ ├── authenticated.png
│ │ │ ├── authorize.png
│ │ │ └── registration.png
│ │ ├── looker_gemini.md
│ │ └── looker_mcp_inspector
│ │ ├── _index.md
│ │ ├── inspector_tools.png
│ │ └── inspector.png
│ └── sdks
│ ├── _index.md
│ ├── go-sdk.md
│ ├── js-sdk.md
│ └── python-sdk.md
├── gemini-extension.json
├── go.mod
├── go.sum
├── internal
│ ├── auth
│ │ ├── auth.go
│ │ └── google
│ │ └── google.go
│ ├── log
│ │ ├── handler.go
│ │ ├── log_test.go
│ │ ├── log.go
│ │ └── logger.go
│ ├── prebuiltconfigs
│ │ ├── prebuiltconfigs_test.go
│ │ ├── prebuiltconfigs.go
│ │ └── tools
│ │ ├── alloydb-postgres-admin.yaml
│ │ ├── alloydb-postgres-observability.yaml
│ │ ├── alloydb-postgres.yaml
│ │ ├── bigquery.yaml
│ │ ├── clickhouse.yaml
│ │ ├── cloud-healthcare.yaml
│ │ ├── cloud-sql-mssql-admin.yaml
│ │ ├── cloud-sql-mssql-observability.yaml
│ │ ├── cloud-sql-mssql.yaml
│ │ ├── cloud-sql-mysql-admin.yaml
│ │ ├── cloud-sql-mysql-observability.yaml
│ │ ├── cloud-sql-mysql.yaml
│ │ ├── cloud-sql-postgres-admin.yaml
│ │ ├── cloud-sql-postgres-observability.yaml
│ │ ├── cloud-sql-postgres.yaml
│ │ ├── dataplex.yaml
│ │ ├── elasticsearch.yaml
│ │ ├── firestore.yaml
│ │ ├── looker-conversational-analytics.yaml
│ │ ├── looker.yaml
│ │ ├── mindsdb.yaml
│ │ ├── mssql.yaml
│ │ ├── mysql.yaml
│ │ ├── neo4j.yaml
│ │ ├── oceanbase.yaml
│ │ ├── postgres.yaml
│ │ ├── serverless-spark.yaml
│ │ ├── singlestore.yaml
│ │ ├── spanner-postgres.yaml
│ │ ├── spanner.yaml
│ │ └── sqlite.yaml
│ ├── server
│ │ ├── api_test.go
│ │ ├── api.go
│ │ ├── common_test.go
│ │ ├── config.go
│ │ ├── mcp
│ │ │ ├── jsonrpc
│ │ │ │ ├── jsonrpc_test.go
│ │ │ │ └── jsonrpc.go
│ │ │ ├── mcp.go
│ │ │ ├── util
│ │ │ │ └── lifecycle.go
│ │ │ ├── v20241105
│ │ │ │ ├── method.go
│ │ │ │ └── types.go
│ │ │ ├── v20250326
│ │ │ │ ├── method.go
│ │ │ │ └── types.go
│ │ │ └── v20250618
│ │ │ ├── method.go
│ │ │ └── types.go
│ │ ├── mcp_test.go
│ │ ├── mcp.go
│ │ ├── server_test.go
│ │ ├── server.go
│ │ ├── static
│ │ │ ├── assets
│ │ │ │ └── mcptoolboxlogo.png
│ │ │ ├── css
│ │ │ │ └── style.css
│ │ │ ├── index.html
│ │ │ ├── js
│ │ │ │ ├── auth.js
│ │ │ │ ├── loadTools.js
│ │ │ │ ├── mainContent.js
│ │ │ │ ├── navbar.js
│ │ │ │ ├── runTool.js
│ │ │ │ ├── toolDisplay.js
│ │ │ │ ├── tools.js
│ │ │ │ └── toolsets.js
│ │ │ ├── tools.html
│ │ │ └── toolsets.html
│ │ ├── web_test.go
│ │ └── web.go
│ ├── sources
│ │ ├── alloydbadmin
│ │ │ ├── alloydbadmin_test.go
│ │ │ └── alloydbadmin.go
│ │ ├── alloydbpg
│ │ │ ├── alloydb_pg_test.go
│ │ │ └── alloydb_pg.go
│ │ ├── bigquery
│ │ │ ├── bigquery_test.go
│ │ │ ├── bigquery.go
│ │ │ └── cache.go
│ │ ├── bigtable
│ │ │ ├── bigtable_test.go
│ │ │ └── bigtable.go
│ │ ├── cassandra
│ │ │ ├── cassandra_test.go
│ │ │ └── cassandra.go
│ │ ├── clickhouse
│ │ │ ├── clickhouse_test.go
│ │ │ └── clickhouse.go
│ │ ├── cloudhealthcare
│ │ │ ├── cloud_healthcare_test.go
│ │ │ └── cloud_healthcare.go
│ │ ├── cloudmonitoring
│ │ │ ├── cloud_monitoring_test.go
│ │ │ └── cloud_monitoring.go
│ │ ├── cloudsqladmin
│ │ │ ├── cloud_sql_admin_test.go
│ │ │ └── cloud_sql_admin.go
│ │ ├── cloudsqlmssql
│ │ │ ├── cloud_sql_mssql_test.go
│ │ │ └── cloud_sql_mssql.go
│ │ ├── cloudsqlmysql
│ │ │ ├── cloud_sql_mysql_test.go
│ │ │ └── cloud_sql_mysql.go
│ │ ├── cloudsqlpg
│ │ │ ├── cloud_sql_pg_test.go
│ │ │ └── cloud_sql_pg.go
│ │ ├── couchbase
│ │ │ ├── couchbase_test.go
│ │ │ └── couchbase.go
│ │ ├── dataplex
│ │ │ ├── dataplex_test.go
│ │ │ └── dataplex.go
│ │ ├── dgraph
│ │ │ ├── dgraph_test.go
│ │ │ └── dgraph.go
│ │ ├── dialect.go
│ │ ├── elasticsearch
│ │ │ ├── elasticsearch_test.go
│ │ │ └── elasticsearch.go
│ │ ├── firebird
│ │ │ ├── firebird_test.go
│ │ │ └── firebird.go
│ │ ├── firestore
│ │ │ ├── firestore_test.go
│ │ │ └── firestore.go
│ │ ├── http
│ │ │ ├── http_test.go
│ │ │ └── http.go
│ │ ├── ip_type.go
│ │ ├── looker
│ │ │ ├── looker_test.go
│ │ │ └── looker.go
│ │ ├── mindsdb
│ │ │ ├── mindsdb_test.go
│ │ │ └── mindsdb.go
│ │ ├── mongodb
│ │ │ ├── mongodb_test.go
│ │ │ └── mongodb.go
│ │ ├── mssql
│ │ │ ├── mssql_test.go
│ │ │ └── mssql.go
│ │ ├── mysql
│ │ │ ├── mysql_test.go
│ │ │ └── mysql.go
│ │ ├── neo4j
│ │ │ ├── neo4j_test.go
│ │ │ └── neo4j.go
│ │ ├── oceanbase
│ │ │ ├── oceanbase_test.go
│ │ │ └── oceanbase.go
│ │ ├── oracle
│ │ │ └── oracle.go
│ │ ├── postgres
│ │ │ ├── postgres_test.go
│ │ │ └── postgres.go
│ │ ├── redis
│ │ │ ├── redis_test.go
│ │ │ └── redis.go
│ │ ├── serverlessspark
│ │ │ ├── serverlessspark_test.go
│ │ │ └── serverlessspark.go
│ │ ├── singlestore
│ │ │ ├── singlestore_test.go
│ │ │ └── singlestore.go
│ │ ├── sources.go
│ │ ├── spanner
│ │ │ ├── spanner_test.go
│ │ │ └── spanner.go
│ │ ├── sqlite
│ │ │ ├── sqlite_test.go
│ │ │ └── sqlite.go
│ │ ├── tidb
│ │ │ ├── tidb_test.go
│ │ │ └── tidb.go
│ │ ├── trino
│ │ │ ├── trino_test.go
│ │ │ └── trino.go
│ │ ├── util.go
│ │ ├── valkey
│ │ │ ├── valkey_test.go
│ │ │ └── valkey.go
│ │ └── yugabytedb
│ │ ├── yugabytedb_test.go
│ │ └── yugabytedb.go
│ ├── telemetry
│ │ ├── instrumentation.go
│ │ └── telemetry.go
│ ├── testutils
│ │ └── testutils.go
│ ├── tools
│ │ ├── alloydb
│ │ │ ├── alloydbcreatecluster
│ │ │ │ ├── alloydbcreatecluster_test.go
│ │ │ │ └── alloydbcreatecluster.go
│ │ │ ├── alloydbcreateinstance
│ │ │ │ ├── alloydbcreateinstance_test.go
│ │ │ │ └── alloydbcreateinstance.go
│ │ │ ├── alloydbcreateuser
│ │ │ │ ├── alloydbcreateuser_test.go
│ │ │ │ └── alloydbcreateuser.go
│ │ │ ├── alloydbgetcluster
│ │ │ │ ├── alloydbgetcluster_test.go
│ │ │ │ └── alloydbgetcluster.go
│ │ │ ├── alloydbgetinstance
│ │ │ │ ├── alloydbgetinstance_test.go
│ │ │ │ └── alloydbgetinstance.go
│ │ │ ├── alloydbgetuser
│ │ │ │ ├── alloydbgetuser_test.go
│ │ │ │ └── alloydbgetuser.go
│ │ │ ├── alloydblistclusters
│ │ │ │ ├── alloydblistclusters_test.go
│ │ │ │ └── alloydblistclusters.go
│ │ │ ├── alloydblistinstances
│ │ │ │ ├── alloydblistinstances_test.go
│ │ │ │ └── alloydblistinstances.go
│ │ │ ├── alloydblistusers
│ │ │ │ ├── alloydblistusers_test.go
│ │ │ │ └── alloydblistusers.go
│ │ │ └── alloydbwaitforoperation
│ │ │ ├── alloydbwaitforoperation_test.go
│ │ │ └── alloydbwaitforoperation.go
│ │ ├── alloydbainl
│ │ │ ├── alloydbainl_test.go
│ │ │ └── alloydbainl.go
│ │ ├── bigquery
│ │ │ ├── bigqueryanalyzecontribution
│ │ │ │ ├── bigqueryanalyzecontribution_test.go
│ │ │ │ └── bigqueryanalyzecontribution.go
│ │ │ ├── bigquerycommon
│ │ │ │ ├── table_name_parser_test.go
│ │ │ │ ├── table_name_parser.go
│ │ │ │ └── util.go
│ │ │ ├── bigqueryconversationalanalytics
│ │ │ │ ├── bigqueryconversationalanalytics_test.go
│ │ │ │ └── bigqueryconversationalanalytics.go
│ │ │ ├── bigqueryexecutesql
│ │ │ │ ├── bigqueryexecutesql_test.go
│ │ │ │ └── bigqueryexecutesql.go
│ │ │ ├── bigqueryforecast
│ │ │ │ ├── bigqueryforecast_test.go
│ │ │ │ └── bigqueryforecast.go
│ │ │ ├── bigquerygetdatasetinfo
│ │ │ │ ├── bigquerygetdatasetinfo_test.go
│ │ │ │ └── bigquerygetdatasetinfo.go
│ │ │ ├── bigquerygettableinfo
│ │ │ │ ├── bigquerygettableinfo_test.go
│ │ │ │ └── bigquerygettableinfo.go
│ │ │ ├── bigquerylistdatasetids
│ │ │ │ ├── bigquerylistdatasetids_test.go
│ │ │ │ └── bigquerylistdatasetids.go
│ │ │ ├── bigquerylisttableids
│ │ │ │ ├── bigquerylisttableids_test.go
│ │ │ │ └── bigquerylisttableids.go
│ │ │ ├── bigquerysearchcatalog
│ │ │ │ ├── bigquerysearchcatalog_test.go
│ │ │ │ └── bigquerysearchcatalog.go
│ │ │ └── bigquerysql
│ │ │ ├── bigquerysql_test.go
│ │ │ └── bigquerysql.go
│ │ ├── bigtable
│ │ │ ├── bigtable_test.go
│ │ │ └── bigtable.go
│ │ ├── cassandra
│ │ │ └── cassandracql
│ │ │ ├── cassandracql_test.go
│ │ │ └── cassandracql.go
│ │ ├── clickhouse
│ │ │ ├── clickhouseexecutesql
│ │ │ │ ├── clickhouseexecutesql_test.go
│ │ │ │ └── clickhouseexecutesql.go
│ │ │ ├── clickhouselistdatabases
│ │ │ │ ├── clickhouselistdatabases_test.go
│ │ │ │ └── clickhouselistdatabases.go
│ │ │ ├── clickhouselisttables
│ │ │ │ ├── clickhouselisttables_test.go
│ │ │ │ └── clickhouselisttables.go
│ │ │ └── clickhousesql
│ │ │ ├── clickhousesql_test.go
│ │ │ └── clickhousesql.go
│ │ ├── cloudhealthcare
│ │ │ ├── cloudhealthcarefhirfetchpage
│ │ │ │ ├── cloudhealthcarefhirfetchpage_test.go
│ │ │ │ └── cloudhealthcarefhirfetchpage.go
│ │ │ ├── cloudhealthcarefhirpatienteverything
│ │ │ │ ├── cloudhealthcarefhirpatienteverything_test.go
│ │ │ │ └── cloudhealthcarefhirpatienteverything.go
│ │ │ ├── cloudhealthcarefhirpatientsearch
│ │ │ │ ├── cloudhealthcarefhirpatientsearch_test.go
│ │ │ │ └── cloudhealthcarefhirpatientsearch.go
│ │ │ ├── cloudhealthcaregetdataset
│ │ │ │ ├── cloudhealthcaregetdataset_test.go
│ │ │ │ └── cloudhealthcaregetdataset.go
│ │ │ ├── cloudhealthcaregetdicomstore
│ │ │ │ ├── cloudhealthcaregetdicomstore_test.go
│ │ │ │ └── cloudhealthcaregetdicomstore.go
│ │ │ ├── cloudhealthcaregetdicomstoremetrics
│ │ │ │ ├── cloudhealthcaregetdicomstoremetrics_test.go
│ │ │ │ └── cloudhealthcaregetdicomstoremetrics.go
│ │ │ ├── cloudhealthcaregetfhirresource
│ │ │ │ ├── cloudhealthcaregetfhirresource_test.go
│ │ │ │ └── cloudhealthcaregetfhirresource.go
│ │ │ ├── cloudhealthcaregetfhirstore
│ │ │ │ ├── cloudhealthcaregetfhirstore_test.go
│ │ │ │ └── cloudhealthcaregetfhirstore.go
│ │ │ ├── cloudhealthcaregetfhirstoremetrics
│ │ │ │ ├── cloudhealthcaregetfhirstoremetrics_test.go
│ │ │ │ └── cloudhealthcaregetfhirstoremetrics.go
│ │ │ ├── cloudhealthcarelistdicomstores
│ │ │ │ ├── cloudhealthcarelistdicomstores_test.go
│ │ │ │ └── cloudhealthcarelistdicomstores.go
│ │ │ ├── cloudhealthcarelistfhirstores
│ │ │ │ ├── cloudhealthcarelistfhirstores_test.go
│ │ │ │ └── cloudhealthcarelistfhirstores.go
│ │ │ ├── cloudhealthcareretrieverendereddicominstance
│ │ │ │ ├── cloudhealthcareretrieverendereddicominstance_test.go
│ │ │ │ └── cloudhealthcareretrieverendereddicominstance.go
│ │ │ ├── cloudhealthcaresearchdicominstances
│ │ │ │ ├── cloudhealthcaresearchdicominstances_test.go
│ │ │ │ └── cloudhealthcaresearchdicominstances.go
│ │ │ ├── cloudhealthcaresearchdicomseries
│ │ │ │ ├── cloudhealthcaresearchdicomseries_test.go
│ │ │ │ └── cloudhealthcaresearchdicomseries.go
│ │ │ ├── cloudhealthcaresearchdicomstudies
│ │ │ │ ├── cloudhealthcaresearchdicomstudies_test.go
│ │ │ │ └── cloudhealthcaresearchdicomstudies.go
│ │ │ └── common
│ │ │ └── util.go
│ │ ├── cloudmonitoring
│ │ │ ├── cloudmonitoring_test.go
│ │ │ └── cloudmonitoring.go
│ │ ├── cloudsql
│ │ │ ├── cloudsqlcreatedatabase
│ │ │ │ ├── cloudsqlcreatedatabase_test.go
│ │ │ │ └── cloudsqlcreatedatabase.go
│ │ │ ├── cloudsqlcreateusers
│ │ │ │ ├── cloudsqlcreateusers_test.go
│ │ │ │ └── cloudsqlcreateusers.go
│ │ │ ├── cloudsqlgetinstances
│ │ │ │ ├── cloudsqlgetinstances_test.go
│ │ │ │ └── cloudsqlgetinstances.go
│ │ │ ├── cloudsqllistdatabases
│ │ │ │ ├── cloudsqllistdatabases_test.go
│ │ │ │ └── cloudsqllistdatabases.go
│ │ │ ├── cloudsqllistinstances
│ │ │ │ ├── cloudsqllistinstances_test.go
│ │ │ │ └── cloudsqllistinstances.go
│ │ │ └── cloudsqlwaitforoperation
│ │ │ ├── cloudsqlwaitforoperation_test.go
│ │ │ └── cloudsqlwaitforoperation.go
│ │ ├── cloudsqlmssql
│ │ │ └── cloudsqlmssqlcreateinstance
│ │ │ ├── cloudsqlmssqlcreateinstance_test.go
│ │ │ └── cloudsqlmssqlcreateinstance.go
│ │ ├── cloudsqlmysql
│ │ │ └── cloudsqlmysqlcreateinstance
│ │ │ ├── cloudsqlmysqlcreateinstance_test.go
│ │ │ └── cloudsqlmysqlcreateinstance.go
│ │ ├── cloudsqlpg
│ │ │ └── cloudsqlpgcreateinstances
│ │ │ ├── cloudsqlpgcreateinstances_test.go
│ │ │ └── cloudsqlpgcreateinstances.go
│ │ ├── common_test.go
│ │ ├── common.go
│ │ ├── couchbase
│ │ │ ├── couchbase_test.go
│ │ │ └── couchbase.go
│ │ ├── dataform
│ │ │ └── dataformcompilelocal
│ │ │ ├── dataformcompilelocal_test.go
│ │ │ └── dataformcompilelocal.go
│ │ ├── dataplex
│ │ │ ├── dataplexlookupentry
│ │ │ │ ├── dataplexlookupentry_test.go
│ │ │ │ └── dataplexlookupentry.go
│ │ │ ├── dataplexsearchaspecttypes
│ │ │ │ ├── dataplexsearchaspecttypes_test.go
│ │ │ │ └── dataplexsearchaspecttypes.go
│ │ │ └── dataplexsearchentries
│ │ │ ├── dataplexsearchentries_test.go
│ │ │ └── dataplexsearchentries.go
│ │ ├── dgraph
│ │ │ ├── dgraph_test.go
│ │ │ └── dgraph.go
│ │ ├── elasticsearch
│ │ │ └── elasticsearchesql
│ │ │ ├── elasticsearchesql_test.go
│ │ │ └── elasticsearchesql.go
│ │ ├── firebird
│ │ │ ├── firebirdexecutesql
│ │ │ │ ├── firebirdexecutesql_test.go
│ │ │ │ └── firebirdexecutesql.go
│ │ │ └── firebirdsql
│ │ │ ├── firebirdsql_test.go
│ │ │ └── firebirdsql.go
│ │ ├── firestore
│ │ │ ├── firestoreadddocuments
│ │ │ │ ├── firestoreadddocuments_test.go
│ │ │ │ └── firestoreadddocuments.go
│ │ │ ├── firestoredeletedocuments
│ │ │ │ ├── firestoredeletedocuments_test.go
│ │ │ │ └── firestoredeletedocuments.go
│ │ │ ├── firestoregetdocuments
│ │ │ │ ├── firestoregetdocuments_test.go
│ │ │ │ └── firestoregetdocuments.go
│ │ │ ├── firestoregetrules
│ │ │ │ ├── firestoregetrules_test.go
│ │ │ │ └── firestoregetrules.go
│ │ │ ├── firestorelistcollections
│ │ │ │ ├── firestorelistcollections_test.go
│ │ │ │ └── firestorelistcollections.go
│ │ │ ├── firestorequery
│ │ │ │ ├── firestorequery_test.go
│ │ │ │ └── firestorequery.go
│ │ │ ├── firestorequerycollection
│ │ │ │ ├── firestorequerycollection_test.go
│ │ │ │ └── firestorequerycollection.go
│ │ │ ├── firestoreupdatedocument
│ │ │ │ ├── firestoreupdatedocument_test.go
│ │ │ │ └── firestoreupdatedocument.go
│ │ │ ├── firestorevalidaterules
│ │ │ │ ├── firestorevalidaterules_test.go
│ │ │ │ └── firestorevalidaterules.go
│ │ │ └── util
│ │ │ ├── converter_test.go
│ │ │ ├── converter.go
│ │ │ ├── validator_test.go
│ │ │ └── validator.go
│ │ ├── http
│ │ │ ├── http_test.go
│ │ │ └── http.go
│ │ ├── http_method.go
│ │ ├── looker
│ │ │ ├── lookeradddashboardelement
│ │ │ │ ├── lookeradddashboardelement_test.go
│ │ │ │ └── lookeradddashboardelement.go
│ │ │ ├── lookercommon
│ │ │ │ ├── lookercommon_test.go
│ │ │ │ └── lookercommon.go
│ │ │ ├── lookerconversationalanalytics
│ │ │ │ ├── lookerconversationalanalytics_test.go
│ │ │ │ └── lookerconversationalanalytics.go
│ │ │ ├── lookercreateprojectfile
│ │ │ │ ├── lookercreateprojectfile_test.go
│ │ │ │ └── lookercreateprojectfile.go
│ │ │ ├── lookerdeleteprojectfile
│ │ │ │ ├── lookerdeleteprojectfile_test.go
│ │ │ │ └── lookerdeleteprojectfile.go
│ │ │ ├── lookerdevmode
│ │ │ │ ├── lookerdevmode_test.go
│ │ │ │ └── lookerdevmode.go
│ │ │ ├── lookergetconnectiondatabases
│ │ │ │ ├── lookergetconnectiondatabases_test.go
│ │ │ │ └── lookergetconnectiondatabases.go
│ │ │ ├── lookergetconnections
│ │ │ │ ├── lookergetconnections_test.go
│ │ │ │ └── lookergetconnections.go
│ │ │ ├── lookergetconnectionschemas
│ │ │ │ ├── lookergetconnectionschemas_test.go
│ │ │ │ └── lookergetconnectionschemas.go
│ │ │ ├── lookergetconnectiontablecolumns
│ │ │ │ ├── lookergetconnectiontablecolumns_test.go
│ │ │ │ └── lookergetconnectiontablecolumns.go
│ │ │ ├── lookergetconnectiontables
│ │ │ │ ├── lookergetconnectiontables_test.go
│ │ │ │ └── lookergetconnectiontables.go
│ │ │ ├── lookergetdashboards
│ │ │ │ ├── lookergetdashboards_test.go
│ │ │ │ └── lookergetdashboards.go
│ │ │ ├── lookergetdimensions
│ │ │ │ ├── lookergetdimensions_test.go
│ │ │ │ └── lookergetdimensions.go
│ │ │ ├── lookergetexplores
│ │ │ │ ├── lookergetexplores_test.go
│ │ │ │ └── lookergetexplores.go
│ │ │ ├── lookergetfilters
│ │ │ │ ├── lookergetfilters_test.go
│ │ │ │ └── lookergetfilters.go
│ │ │ ├── lookergetlooks
│ │ │ │ ├── lookergetlooks_test.go
│ │ │ │ └── lookergetlooks.go
│ │ │ ├── lookergetmeasures
│ │ │ │ ├── lookergetmeasures_test.go
│ │ │ │ └── lookergetmeasures.go
│ │ │ ├── lookergetmodels
│ │ │ │ ├── lookergetmodels_test.go
│ │ │ │ └── lookergetmodels.go
│ │ │ ├── lookergetparameters
│ │ │ │ ├── lookergetparameters_test.go
│ │ │ │ └── lookergetparameters.go
│ │ │ ├── lookergetprojectfile
│ │ │ │ ├── lookergetprojectfile_test.go
│ │ │ │ └── lookergetprojectfile.go
│ │ │ ├── lookergetprojectfiles
│ │ │ │ ├── lookergetprojectfiles_test.go
│ │ │ │ └── lookergetprojectfiles.go
│ │ │ ├── lookergetprojects
│ │ │ │ ├── lookergetprojects_test.go
│ │ │ │ └── lookergetprojects.go
│ │ │ ├── lookerhealthanalyze
│ │ │ │ ├── lookerhealthanalyze_test.go
│ │ │ │ └── lookerhealthanalyze.go
│ │ │ ├── lookerhealthpulse
│ │ │ │ ├── lookerhealthpulse_test.go
│ │ │ │ └── lookerhealthpulse.go
│ │ │ ├── lookerhealthvacuum
│ │ │ │ ├── lookerhealthvacuum_test.go
│ │ │ │ └── lookerhealthvacuum.go
│ │ │ ├── lookermakedashboard
│ │ │ │ ├── lookermakedashboard_test.go
│ │ │ │ └── lookermakedashboard.go
│ │ │ ├── lookermakelook
│ │ │ │ ├── lookermakelook_test.go
│ │ │ │ └── lookermakelook.go
│ │ │ ├── lookerquery
│ │ │ │ ├── lookerquery_test.go
│ │ │ │ └── lookerquery.go
│ │ │ ├── lookerquerysql
│ │ │ │ ├── lookerquerysql_test.go
│ │ │ │ └── lookerquerysql.go
│ │ │ ├── lookerqueryurl
│ │ │ │ ├── lookerqueryurl_test.go
│ │ │ │ └── lookerqueryurl.go
│ │ │ ├── lookerrundashboard
│ │ │ │ ├── lookerrundashboard_test.go
│ │ │ │ └── lookerrundashboard.go
│ │ │ ├── lookerrunlook
│ │ │ │ ├── lookerrunlook_test.go
│ │ │ │ └── lookerrunlook.go
│ │ │ └── lookerupdateprojectfile
│ │ │ ├── lookerupdateprojectfile_test.go
│ │ │ └── lookerupdateprojectfile.go
│ │ ├── mindsdb
│ │ │ ├── mindsdbexecutesql
│ │ │ │ ├── mindsdbexecutesql_test.go
│ │ │ │ └── mindsdbexecutesql.go
│ │ │ └── mindsdbsql
│ │ │ ├── mindsdbsql_test.go
│ │ │ └── mindsdbsql.go
│ │ ├── mongodb
│ │ │ ├── mongodbaggregate
│ │ │ │ ├── mongodbaggregate_test.go
│ │ │ │ └── mongodbaggregate.go
│ │ │ ├── mongodbdeletemany
│ │ │ │ ├── mongodbdeletemany_test.go
│ │ │ │ └── mongodbdeletemany.go
│ │ │ ├── mongodbdeleteone
│ │ │ │ ├── mongodbdeleteone_test.go
│ │ │ │ └── mongodbdeleteone.go
│ │ │ ├── mongodbfind
│ │ │ │ ├── mongodbfind_test.go
│ │ │ │ └── mongodbfind.go
│ │ │ ├── mongodbfindone
│ │ │ │ ├── mongodbfindone_test.go
│ │ │ │ └── mongodbfindone.go
│ │ │ ├── mongodbinsertmany
│ │ │ │ ├── mongodbinsertmany_test.go
│ │ │ │ └── mongodbinsertmany.go
│ │ │ ├── mongodbinsertone
│ │ │ │ ├── mongodbinsertone_test.go
│ │ │ │ └── mongodbinsertone.go
│ │ │ ├── mongodbupdatemany
│ │ │ │ ├── mongodbupdatemany_test.go
│ │ │ │ └── mongodbupdatemany.go
│ │ │ └── mongodbupdateone
│ │ │ ├── mongodbupdateone_test.go
│ │ │ └── mongodbupdateone.go
│ │ ├── mssql
│ │ │ ├── mssqlexecutesql
│ │ │ │ ├── mssqlexecutesql_test.go
│ │ │ │ └── mssqlexecutesql.go
│ │ │ ├── mssqllisttables
│ │ │ │ ├── mssqllisttables_test.go
│ │ │ │ └── mssqllisttables.go
│ │ │ └── mssqlsql
│ │ │ ├── mssqlsql_test.go
│ │ │ └── mssqlsql.go
│ │ ├── mysql
│ │ │ ├── mysqlcommon
│ │ │ │ └── mysqlcommon.go
│ │ │ ├── mysqlexecutesql
│ │ │ │ ├── mysqlexecutesql_test.go
│ │ │ │ └── mysqlexecutesql.go
│ │ │ ├── mysqllistactivequeries
│ │ │ │ ├── mysqllistactivequeries_test.go
│ │ │ │ └── mysqllistactivequeries.go
│ │ │ ├── mysqllisttablefragmentation
│ │ │ │ ├── mysqllisttablefragmentation_test.go
│ │ │ │ └── mysqllisttablefragmentation.go
│ │ │ ├── mysqllisttables
│ │ │ │ ├── mysqllisttables_test.go
│ │ │ │ └── mysqllisttables.go
│ │ │ ├── mysqllisttablesmissinguniqueindexes
│ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go
│ │ │ │ └── mysqllisttablesmissinguniqueindexes.go
│ │ │ └── mysqlsql
│ │ │ ├── mysqlsql_test.go
│ │ │ └── mysqlsql.go
│ │ ├── neo4j
│ │ │ ├── neo4jcypher
│ │ │ │ ├── neo4jcypher_test.go
│ │ │ │ └── neo4jcypher.go
│ │ │ ├── neo4jexecutecypher
│ │ │ │ ├── classifier
│ │ │ │ │ ├── classifier_test.go
│ │ │ │ │ └── classifier.go
│ │ │ │ ├── neo4jexecutecypher_test.go
│ │ │ │ └── neo4jexecutecypher.go
│ │ │ └── neo4jschema
│ │ │ ├── cache
│ │ │ │ ├── cache_test.go
│ │ │ │ └── cache.go
│ │ │ ├── helpers
│ │ │ │ ├── helpers_test.go
│ │ │ │ └── helpers.go
│ │ │ ├── neo4jschema_test.go
│ │ │ ├── neo4jschema.go
│ │ │ └── types
│ │ │ └── types.go
│ │ ├── oceanbase
│ │ │ ├── oceanbaseexecutesql
│ │ │ │ ├── oceanbaseexecutesql_test.go
│ │ │ │ └── oceanbaseexecutesql.go
│ │ │ └── oceanbasesql
│ │ │ ├── oceanbasesql_test.go
│ │ │ └── oceanbasesql.go
│ │ ├── oracle
│ │ │ ├── oracleexecutesql
│ │ │ │ └── oracleexecutesql.go
│ │ │ └── oraclesql
│ │ │ └── oraclesql.go
│ │ ├── parameters_test.go
│ │ ├── parameters.go
│ │ ├── postgres
│ │ │ ├── postgresexecutesql
│ │ │ │ ├── postgresexecutesql_test.go
│ │ │ │ └── postgresexecutesql.go
│ │ │ ├── postgreslistactivequeries
│ │ │ │ ├── postgreslistactivequeries_test.go
│ │ │ │ └── postgreslistactivequeries.go
│ │ │ ├── postgreslistavailableextensions
│ │ │ │ ├── postgreslistavailableextensions_test.go
│ │ │ │ └── postgreslistavailableextensions.go
│ │ │ ├── postgreslistinstalledextensions
│ │ │ │ ├── postgreslistinstalledextensions_test.go
│ │ │ │ └── postgreslistinstalledextensions.go
│ │ │ ├── postgreslistschemas
│ │ │ │ ├── postgreslistschemas_test.go
│ │ │ │ └── postgreslistschemas.go
│ │ │ ├── postgreslisttables
│ │ │ │ ├── postgreslisttables_test.go
│ │ │ │ └── postgreslisttables.go
│ │ │ ├── postgreslistviews
│ │ │ │ ├── postgreslistviews_test.go
│ │ │ │ └── postgreslistviews.go
│ │ │ └── postgressql
│ │ │ ├── postgressql_test.go
│ │ │ └── postgressql.go
│ │ ├── redis
│ │ │ ├── redis_test.go
│ │ │ └── redis.go
│ │ ├── serverlessspark
│ │ │ ├── serverlesssparkcancelbatch
│ │ │ │ ├── serverlesssparkcancelbatch_test.go
│ │ │ │ └── serverlesssparkcancelbatch.go
│ │ │ ├── serverlesssparkgetbatch
│ │ │ │ ├── serverlesssparkgetbatch_test.go
│ │ │ │ └── serverlesssparkgetbatch.go
│ │ │ └── serverlesssparklistbatches
│ │ │ ├── serverlesssparklistbatches_test.go
│ │ │ └── serverlesssparklistbatches.go
│ │ ├── singlestore
│ │ │ ├── singlestoreexecutesql
│ │ │ │ ├── singlestoreexecutesql_test.go
│ │ │ │ └── singlestoreexecutesql.go
│ │ │ └── singlestoresql
│ │ │ ├── singlestoresql_test.go
│ │ │ └── singlestoresql.go
│ │ ├── spanner
│ │ │ ├── spannerexecutesql
│ │ │ │ ├── spannerexecutesql_test.go
│ │ │ │ └── spannerexecutesql.go
│ │ │ ├── spannerlisttables
│ │ │ │ ├── spannerlisttables_test.go
│ │ │ │ └── spannerlisttables.go
│ │ │ └── spannersql
│ │ │ ├── spanner_test.go
│ │ │ └── spannersql.go
│ │ ├── sqlite
│ │ │ ├── sqliteexecutesql
│ │ │ │ ├── sqliteexecutesql_test.go
│ │ │ │ └── sqliteexecutesql.go
│ │ │ └── sqlitesql
│ │ │ ├── sqlitesql_test.go
│ │ │ └── sqlitesql.go
│ │ ├── tidb
│ │ │ ├── tidbexecutesql
│ │ │ │ ├── tidbexecutesql_test.go
│ │ │ │ └── tidbexecutesql.go
│ │ │ └── tidbsql
│ │ │ ├── tidbsql_test.go
│ │ │ └── tidbsql.go
│ │ ├── tools_test.go
│ │ ├── tools.go
│ │ ├── toolsets.go
│ │ ├── trino
│ │ │ ├── trinoexecutesql
│ │ │ │ ├── trinoexecutesql_test.go
│ │ │ │ └── trinoexecutesql.go
│ │ │ └── trinosql
│ │ │ ├── trinosql_test.go
│ │ │ └── trinosql.go
│ │ ├── utility
│ │ │ └── wait
│ │ │ ├── wait_test.go
│ │ │ └── wait.go
│ │ ├── valkey
│ │ │ ├── valkey_test.go
│ │ │ └── valkey.go
│ │ └── yugabytedbsql
│ │ ├── yugabytedbsql_test.go
│ │ └── yugabytedbsql.go
│ └── util
│ ├── orderedmap
│ │ ├── orderedmap_test.go
│ │ └── orderedmap.go
│ └── util.go
├── LICENSE
├── logo.png
├── main.go
├── MCP-TOOLBOX-EXTENSION.md
├── README.md
└── tests
├── alloydb
│ ├── alloydb_integration_test.go
│ └── alloydb_wait_for_operation_test.go
├── alloydbainl
│ └── alloydb_ai_nl_integration_test.go
├── alloydbpg
│ └── alloydb_pg_integration_test.go
├── auth.go
├── bigquery
│ └── bigquery_integration_test.go
├── bigtable
│ └── bigtable_integration_test.go
├── cassandra
│ └── cassandra_integration_test.go
├── clickhouse
│ └── clickhouse_integration_test.go
├── cloudhealthcare
│ └── cloud_healthcare_integration_test.go
├── cloudmonitoring
│ └── cloud_monitoring_integration_test.go
├── cloudsql
│ ├── cloud_sql_create_database_test.go
│ ├── cloud_sql_create_users_test.go
│ ├── cloud_sql_get_instances_test.go
│ ├── cloud_sql_list_databases_test.go
│ ├── cloudsql_list_instances_test.go
│ └── cloudsql_wait_for_operation_test.go
├── cloudsqlmssql
│ ├── cloud_sql_mssql_create_instance_integration_test.go
│ └── cloud_sql_mssql_integration_test.go
├── cloudsqlmysql
│ ├── cloud_sql_mysql_create_instance_integration_test.go
│ └── cloud_sql_mysql_integration_test.go
├── cloudsqlpg
│ ├── cloud_sql_pg_create_instances_test.go
│ └── cloud_sql_pg_integration_test.go
├── common.go
├── couchbase
│ └── couchbase_integration_test.go
├── dataform
│ └── dataform_integration_test.go
├── dataplex
│ └── dataplex_integration_test.go
├── dgraph
│ └── dgraph_integration_test.go
├── elasticsearch
│ └── elasticsearch_integration_test.go
├── firebird
│ └── firebird_integration_test.go
├── firestore
│ └── firestore_integration_test.go
├── http
│ └── http_integration_test.go
├── looker
│ └── looker_integration_test.go
├── mindsdb
│ └── mindsdb_integration_test.go
├── mongodb
│ └── mongodb_integration_test.go
├── mssql
│ └── mssql_integration_test.go
├── mysql
│ └── mysql_integration_test.go
├── neo4j
│ └── neo4j_integration_test.go
├── oceanbase
│ └── oceanbase_integration_test.go
├── option.go
├── oracle
│ └── oracle_integration_test.go
├── postgres
│ └── postgres_integration_test.go
├── redis
│ └── redis_test.go
├── server.go
├── serverlessspark
│ └── serverless_spark_integration_test.go
├── singlestore
│ └── singlestore_integration_test.go
├── source.go
├── spanner
│ └── spanner_integration_test.go
├── sqlite
│ └── sqlite_integration_test.go
├── tidb
│ └── tidb_integration_test.go
├── tool.go
├── trino
│ └── trino_integration_test.go
├── utility
│ └── wait_integration_test.go
├── valkey
│ └── valkey_test.go
└── yugabytedb
└── yugabytedb_integration_test.go
```
# Files
--------------------------------------------------------------------------------
/internal/tools/parameters_test.go:
--------------------------------------------------------------------------------
```go
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package tools_test
16 |
17 | import (
18 | "bytes"
19 | "encoding/json"
20 | "math"
21 | "reflect"
22 | "slices"
23 | "strings"
24 | "testing"
25 |
26 | "github.com/goccy/go-yaml"
27 | "github.com/google/go-cmp/cmp"
28 | "github.com/googleapis/genai-toolbox/internal/testutils"
29 | "github.com/googleapis/genai-toolbox/internal/tools"
30 | )
31 |
32 | func TestParametersMarshal(t *testing.T) {
33 | ctx, err := testutils.ContextWithNewLogger()
34 | if err != nil {
35 | t.Fatalf("unexpected error: %s", err)
36 | }
37 | tcs := []struct {
38 | name string
39 | in []map[string]any
40 | want tools.Parameters
41 | }{
42 | {
43 | name: "string",
44 | in: []map[string]any{
45 | {
46 | "name": "my_string",
47 | "type": "string",
48 | "description": "this param is a string",
49 | },
50 | },
51 | want: tools.Parameters{
52 | tools.NewStringParameter("my_string", "this param is a string"),
53 | },
54 | },
55 | {
56 | name: "string not required",
57 | in: []map[string]any{
58 | {
59 | "name": "my_string",
60 | "type": "string",
61 | "description": "this param is a string",
62 | "required": false,
63 | },
64 | },
65 | want: tools.Parameters{
66 | tools.NewStringParameterWithRequired("my_string", "this param is a string", false),
67 | },
68 | },
69 | {
70 | name: "int",
71 | in: []map[string]any{
72 | {
73 | "name": "my_integer",
74 | "type": "integer",
75 | "description": "this param is an int",
76 | },
77 | },
78 | want: tools.Parameters{
79 | tools.NewIntParameter("my_integer", "this param is an int"),
80 | },
81 | },
82 | {
83 | name: "int not required",
84 | in: []map[string]any{
85 | {
86 | "name": "my_integer",
87 | "type": "integer",
88 | "description": "this param is an int",
89 | "required": false,
90 | },
91 | },
92 | want: tools.Parameters{
93 | tools.NewIntParameterWithRequired("my_integer", "this param is an int", false),
94 | },
95 | },
96 | {
97 | name: "float",
98 | in: []map[string]any{
99 | {
100 | "name": "my_float",
101 | "type": "float",
102 | "description": "my param is a float",
103 | },
104 | },
105 | want: tools.Parameters{
106 | tools.NewFloatParameter("my_float", "my param is a float"),
107 | },
108 | },
109 | {
110 | name: "float not required",
111 | in: []map[string]any{
112 | {
113 | "name": "my_float",
114 | "type": "float",
115 | "description": "my param is a float",
116 | "required": false,
117 | },
118 | },
119 | want: tools.Parameters{
120 | tools.NewFloatParameterWithRequired("my_float", "my param is a float", false),
121 | },
122 | },
123 | {
124 | name: "bool",
125 | in: []map[string]any{
126 | {
127 | "name": "my_bool",
128 | "type": "boolean",
129 | "description": "this param is a boolean",
130 | },
131 | },
132 | want: tools.Parameters{
133 | tools.NewBooleanParameter("my_bool", "this param is a boolean"),
134 | },
135 | },
136 | {
137 | name: "bool not required",
138 | in: []map[string]any{
139 | {
140 | "name": "my_bool",
141 | "type": "boolean",
142 | "description": "this param is a boolean",
143 | "required": false,
144 | },
145 | },
146 | want: tools.Parameters{
147 | tools.NewBooleanParameterWithRequired("my_bool", "this param is a boolean", false),
148 | },
149 | },
150 | {
151 | name: "string array",
152 | in: []map[string]any{
153 | {
154 | "name": "my_array",
155 | "type": "array",
156 | "description": "this param is an array of strings",
157 | "items": map[string]string{
158 | "name": "my_string",
159 | "type": "string",
160 | "description": "string item",
161 | },
162 | },
163 | },
164 | want: tools.Parameters{
165 | tools.NewArrayParameter("my_array", "this param is an array of strings", tools.NewStringParameter("my_string", "string item")),
166 | },
167 | },
168 | {
169 | name: "string array not required",
170 | in: []map[string]any{
171 | {
172 | "name": "my_array",
173 | "type": "array",
174 | "description": "this param is an array of strings",
175 | "required": false,
176 | "items": map[string]string{
177 | "name": "my_string",
178 | "type": "string",
179 | "description": "string item",
180 | },
181 | },
182 | },
183 | want: tools.Parameters{
184 | tools.NewArrayParameterWithRequired("my_array", "this param is an array of strings", false, tools.NewStringParameter("my_string", "string item")),
185 | },
186 | },
187 | {
188 | name: "float array",
189 | in: []map[string]any{
190 | {
191 | "name": "my_array",
192 | "type": "array",
193 | "description": "this param is an array of floats",
194 | "items": map[string]string{
195 | "name": "my_float",
196 | "type": "float",
197 | "description": "float item",
198 | },
199 | },
200 | },
201 | want: tools.Parameters{
202 | tools.NewArrayParameter("my_array", "this param is an array of floats", tools.NewFloatParameter("my_float", "float item")),
203 | },
204 | },
205 | {
206 | name: "string default",
207 | in: []map[string]any{
208 | {
209 | "name": "my_string",
210 | "type": "string",
211 | "default": "foo",
212 | "description": "this param is a string",
213 | },
214 | },
215 | want: tools.Parameters{
216 | tools.NewStringParameterWithDefault("my_string", "foo", "this param is a string"),
217 | },
218 | },
219 | {
220 | name: "int default",
221 | in: []map[string]any{
222 | {
223 | "name": "my_integer",
224 | "type": "integer",
225 | "default": 5,
226 | "description": "this param is an int",
227 | },
228 | },
229 | want: tools.Parameters{
230 | tools.NewIntParameterWithDefault("my_integer", 5, "this param is an int"),
231 | },
232 | },
233 | {
234 | name: "float default",
235 | in: []map[string]any{
236 | {
237 | "name": "my_float",
238 | "type": "float",
239 | "default": 1.1,
240 | "description": "my param is a float",
241 | },
242 | },
243 | want: tools.Parameters{
244 | tools.NewFloatParameterWithDefault("my_float", 1.1, "my param is a float"),
245 | },
246 | },
247 | {
248 | name: "bool default",
249 | in: []map[string]any{
250 | {
251 | "name": "my_bool",
252 | "type": "boolean",
253 | "default": true,
254 | "description": "this param is a boolean",
255 | },
256 | },
257 | want: tools.Parameters{
258 | tools.NewBooleanParameterWithDefault("my_bool", true, "this param is a boolean"),
259 | },
260 | },
261 | {
262 | name: "string array default",
263 | in: []map[string]any{
264 | {
265 | "name": "my_array",
266 | "type": "array",
267 | "default": []any{"foo", "bar"},
268 | "description": "this param is an array of strings",
269 | "items": map[string]string{
270 | "name": "my_string",
271 | "type": "string",
272 | "description": "string item",
273 | },
274 | },
275 | },
276 | want: tools.Parameters{
277 | tools.NewArrayParameterWithDefault("my_array", []any{"foo", "bar"}, "this param is an array of strings", tools.NewStringParameter("my_string", "string item")),
278 | },
279 | },
280 | {
281 | name: "float array default",
282 | in: []map[string]any{
283 | {
284 | "name": "my_array",
285 | "type": "array",
286 | "default": []any{1.0, 1.1},
287 | "description": "this param is an array of floats",
288 | "items": map[string]string{
289 | "name": "my_float",
290 | "type": "float",
291 | "description": "float item",
292 | },
293 | },
294 | },
295 | want: tools.Parameters{
296 | tools.NewArrayParameterWithDefault("my_array", []any{1.0, 1.1}, "this param is an array of floats", tools.NewFloatParameter("my_float", "float item")),
297 | },
298 | },
299 | {
300 | name: "map with string values",
301 | in: []map[string]any{
302 | {
303 | "name": "my_map",
304 | "type": "map",
305 | "description": "this param is a map of strings",
306 | "valueType": "string",
307 | },
308 | },
309 | want: tools.Parameters{
310 | tools.NewMapParameter("my_map", "this param is a map of strings", "string"),
311 | },
312 | },
313 | {
314 | name: "map not required",
315 | in: []map[string]any{
316 | {
317 | "name": "my_map",
318 | "type": "map",
319 | "description": "this param is a map of strings",
320 | "required": false,
321 | "valueType": "string",
322 | },
323 | },
324 | want: tools.Parameters{
325 | tools.NewMapParameterWithRequired("my_map", "this param is a map of strings", false, "string"),
326 | },
327 | },
328 | {
329 | name: "map with default",
330 | in: []map[string]any{
331 | {
332 | "name": "my_map",
333 | "type": "map",
334 | "description": "this param is a map of strings",
335 | "default": map[string]any{"key1": "val1"},
336 | "valueType": "string",
337 | },
338 | },
339 | want: tools.Parameters{
340 | tools.NewMapParameterWithDefault("my_map", map[string]any{"key1": "val1"}, "this param is a map of strings", "string"),
341 | },
342 | },
343 | {
344 | name: "generic map (no valueType)",
345 | in: []map[string]any{
346 | {
347 | "name": "my_generic_map",
348 | "type": "map",
349 | "description": "this param is a generic map",
350 | },
351 | },
352 | want: tools.Parameters{
353 | tools.NewMapParameter("my_generic_map", "this param is a generic map", ""),
354 | },
355 | },
356 | }
357 | for _, tc := range tcs {
358 | t.Run(tc.name, func(t *testing.T) {
359 | var got tools.Parameters
360 | // parse map to bytes
361 | data, err := yaml.Marshal(tc.in)
362 | if err != nil {
363 | t.Fatalf("unable to marshal input to yaml: %s", err)
364 | }
365 | // parse bytes to object
366 | err = yaml.UnmarshalContext(ctx, data, &got)
367 | if err != nil {
368 | t.Fatalf("unable to unmarshal: %s", err)
369 | }
370 | if diff := cmp.Diff(tc.want, got); diff != "" {
371 | t.Fatalf("incorrect parse: diff %v", diff)
372 | }
373 | })
374 | }
375 | }
376 |
377 | func TestAuthParametersMarshal(t *testing.T) {
378 | ctx, err := testutils.ContextWithNewLogger()
379 | if err != nil {
380 | t.Fatalf("unexpected error: %s", err)
381 | }
382 | authServices := []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"}, {Name: "other-auth-service", Field: "user_id"}}
383 | tcs := []struct {
384 | name string
385 | in []map[string]any
386 | want tools.Parameters
387 | }{
388 | {
389 | name: "string",
390 | in: []map[string]any{
391 | {
392 | "name": "my_string",
393 | "type": "string",
394 | "description": "this param is a string",
395 | "authServices": []map[string]string{
396 | {
397 | "name": "my-google-auth-service",
398 | "field": "user_id",
399 | },
400 | {
401 | "name": "other-auth-service",
402 | "field": "user_id",
403 | },
404 | },
405 | },
406 | },
407 | want: tools.Parameters{
408 | tools.NewStringParameterWithAuth("my_string", "this param is a string", authServices),
409 | },
410 | },
411 | {
412 | name: "string with authServices",
413 | in: []map[string]any{
414 | {
415 | "name": "my_string",
416 | "type": "string",
417 | "description": "this param is a string",
418 | "authServices": []map[string]string{
419 | {
420 | "name": "my-google-auth-service",
421 | "field": "user_id",
422 | },
423 | {
424 | "name": "other-auth-service",
425 | "field": "user_id",
426 | },
427 | },
428 | },
429 | },
430 | want: tools.Parameters{
431 | tools.NewStringParameterWithAuth("my_string", "this param is a string", authServices),
432 | },
433 | },
434 | {
435 | name: "int",
436 | in: []map[string]any{
437 | {
438 | "name": "my_integer",
439 | "type": "integer",
440 | "description": "this param is an int",
441 | "authServices": []map[string]string{
442 | {
443 | "name": "my-google-auth-service",
444 | "field": "user_id",
445 | },
446 | {
447 | "name": "other-auth-service",
448 | "field": "user_id",
449 | },
450 | },
451 | },
452 | },
453 | want: tools.Parameters{
454 | tools.NewIntParameterWithAuth("my_integer", "this param is an int", authServices),
455 | },
456 | },
457 | {
458 | name: "int with authServices",
459 | in: []map[string]any{
460 | {
461 | "name": "my_integer",
462 | "type": "integer",
463 | "description": "this param is an int",
464 | "authServices": []map[string]string{
465 | {
466 | "name": "my-google-auth-service",
467 | "field": "user_id",
468 | },
469 | {
470 | "name": "other-auth-service",
471 | "field": "user_id",
472 | },
473 | },
474 | },
475 | },
476 | want: tools.Parameters{
477 | tools.NewIntParameterWithAuth("my_integer", "this param is an int", authServices),
478 | },
479 | },
480 | {
481 | name: "float",
482 | in: []map[string]any{
483 | {
484 | "name": "my_float",
485 | "type": "float",
486 | "description": "my param is a float",
487 | "authServices": []map[string]string{
488 | {
489 | "name": "my-google-auth-service",
490 | "field": "user_id",
491 | },
492 | {
493 | "name": "other-auth-service",
494 | "field": "user_id",
495 | },
496 | },
497 | },
498 | },
499 | want: tools.Parameters{
500 | tools.NewFloatParameterWithAuth("my_float", "my param is a float", authServices),
501 | },
502 | },
503 | {
504 | name: "float with authServices",
505 | in: []map[string]any{
506 | {
507 | "name": "my_float",
508 | "type": "float",
509 | "description": "my param is a float",
510 | "authServices": []map[string]string{
511 | {
512 | "name": "my-google-auth-service",
513 | "field": "user_id",
514 | },
515 | {
516 | "name": "other-auth-service",
517 | "field": "user_id",
518 | },
519 | },
520 | },
521 | },
522 | want: tools.Parameters{
523 | tools.NewFloatParameterWithAuth("my_float", "my param is a float", authServices),
524 | },
525 | },
526 | {
527 | name: "bool",
528 | in: []map[string]any{
529 | {
530 | "name": "my_bool",
531 | "type": "boolean",
532 | "description": "this param is a boolean",
533 | "authServices": []map[string]string{
534 | {
535 | "name": "my-google-auth-service",
536 | "field": "user_id",
537 | },
538 | {
539 | "name": "other-auth-service",
540 | "field": "user_id",
541 | },
542 | },
543 | },
544 | },
545 | want: tools.Parameters{
546 | tools.NewBooleanParameterWithAuth("my_bool", "this param is a boolean", authServices),
547 | },
548 | },
549 | {
550 | name: "bool with authServices",
551 | in: []map[string]any{
552 | {
553 | "name": "my_bool",
554 | "type": "boolean",
555 | "description": "this param is a boolean",
556 | "authServices": []map[string]string{
557 | {
558 | "name": "my-google-auth-service",
559 | "field": "user_id",
560 | },
561 | {
562 | "name": "other-auth-service",
563 | "field": "user_id",
564 | },
565 | },
566 | },
567 | },
568 | want: tools.Parameters{
569 | tools.NewBooleanParameterWithAuth("my_bool", "this param is a boolean", authServices),
570 | },
571 | },
572 | {
573 | name: "string array",
574 | in: []map[string]any{
575 | {
576 | "name": "my_array",
577 | "type": "array",
578 | "description": "this param is an array of strings",
579 | "items": map[string]string{
580 | "name": "my_string",
581 | "type": "string",
582 | "description": "string item",
583 | },
584 | "authServices": []map[string]string{
585 | {
586 | "name": "my-google-auth-service",
587 | "field": "user_id",
588 | },
589 | {
590 | "name": "other-auth-service",
591 | "field": "user_id",
592 | },
593 | },
594 | },
595 | },
596 | want: tools.Parameters{
597 | tools.NewArrayParameterWithAuth("my_array", "this param is an array of strings", tools.NewStringParameter("my_string", "string item"), authServices),
598 | },
599 | },
600 | {
601 | name: "string array with authServices",
602 | in: []map[string]any{
603 | {
604 | "name": "my_array",
605 | "type": "array",
606 | "description": "this param is an array of strings",
607 | "items": map[string]string{
608 | "name": "my_string",
609 | "type": "string",
610 | "description": "string item",
611 | },
612 | "authServices": []map[string]string{
613 | {
614 | "name": "my-google-auth-service",
615 | "field": "user_id",
616 | },
617 | {
618 | "name": "other-auth-service",
619 | "field": "user_id",
620 | },
621 | },
622 | },
623 | },
624 | want: tools.Parameters{
625 | tools.NewArrayParameterWithAuth("my_array", "this param is an array of strings", tools.NewStringParameter("my_string", "string item"), authServices),
626 | },
627 | },
628 | {
629 | name: "float array",
630 | in: []map[string]any{
631 | {
632 | "name": "my_array",
633 | "type": "array",
634 | "description": "this param is an array of floats",
635 | "items": map[string]string{
636 | "name": "my_float",
637 | "type": "float",
638 | "description": "float item",
639 | },
640 | "authServices": []map[string]string{
641 | {
642 | "name": "my-google-auth-service",
643 | "field": "user_id",
644 | },
645 | {
646 | "name": "other-auth-service",
647 | "field": "user_id",
648 | },
649 | },
650 | },
651 | },
652 | want: tools.Parameters{
653 | tools.NewArrayParameterWithAuth("my_array", "this param is an array of floats", tools.NewFloatParameter("my_float", "float item"), authServices),
654 | },
655 | },
656 | {
657 | name: "map",
658 | in: []map[string]any{
659 | {
660 | "name": "my_map",
661 | "type": "map",
662 | "description": "this param is a map of strings",
663 | "valueType": "string",
664 | "authServices": []map[string]string{
665 | {"name": "my-google-auth-service", "field": "user_id"},
666 | {"name": "other-auth-service", "field": "user_id"},
667 | },
668 | },
669 | },
670 | want: tools.Parameters{
671 | tools.NewMapParameterWithAuth("my_map", "this param is a map of strings", "string", authServices),
672 | },
673 | },
674 | }
675 | for _, tc := range tcs {
676 | t.Run(tc.name, func(t *testing.T) {
677 | var got tools.Parameters
678 | // parse map to bytes
679 | data, err := yaml.Marshal(tc.in)
680 | if err != nil {
681 | t.Fatalf("unable to marshal input to yaml: %s", err)
682 | }
683 | // parse bytes to object
684 | err = yaml.UnmarshalContext(ctx, data, &got)
685 | if err != nil {
686 | t.Fatalf("unable to unmarshal: %s", err)
687 | }
688 | if diff := cmp.Diff(tc.want, got); diff != "" {
689 | t.Fatalf("incorrect parse: diff %v", diff)
690 | }
691 | })
692 | }
693 | }
694 |
695 | func TestParametersParse(t *testing.T) {
696 | intValue := 2
697 | floatValue := 1.5
698 | tcs := []struct {
699 | name string
700 | params tools.Parameters
701 | in map[string]any
702 | want tools.ParamValues
703 | }{
704 | // ... (primitive type tests are unchanged)
705 | {
706 | name: "string",
707 | params: tools.Parameters{
708 | tools.NewStringParameter("my_string", "this param is a string"),
709 | },
710 | in: map[string]any{
711 | "my_string": "hello world",
712 | },
713 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "hello world"}},
714 | },
715 | {
716 | name: "not string",
717 | params: tools.Parameters{
718 | tools.NewStringParameter("my_string", "this param is a string"),
719 | },
720 | in: map[string]any{
721 | "my_string": 4,
722 | },
723 | },
724 | {
725 | name: "string allowed",
726 | params: tools.Parameters{
727 | tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"foo"}),
728 | },
729 | in: map[string]any{
730 | "my_string": "foo",
731 | },
732 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "foo"}},
733 | },
734 | {
735 | name: "string allowed regex",
736 | params: tools.Parameters{
737 | tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"^f.*"}),
738 | },
739 | in: map[string]any{
740 | "my_string": "foo",
741 | },
742 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "foo"}},
743 | },
744 | {
745 | name: "string not allowed",
746 | params: tools.Parameters{
747 | tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"foo"}),
748 | },
749 | in: map[string]any{
750 | "my_string": "bar",
751 | },
752 | },
753 | {
754 | name: "string not allowed regex",
755 | params: tools.Parameters{
756 | tools.NewStringParameterWithAllowedValues("my_string", "this param is a string", []any{"^f.*"}),
757 | },
758 | in: map[string]any{
759 | "my_string": "bar",
760 | },
761 | },
762 | {
763 | name: "string excluded",
764 | params: tools.Parameters{
765 | tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"foo"}),
766 | },
767 | in: map[string]any{
768 | "my_string": "foo",
769 | },
770 | },
771 | {
772 | name: "string excluded regex",
773 | params: tools.Parameters{
774 | tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"^f.*"}),
775 | },
776 | in: map[string]any{
777 | "my_string": "foo",
778 | },
779 | },
780 | {
781 | name: "string not excluded",
782 | params: tools.Parameters{
783 | tools.NewStringParameterWithExcludedValues("my_string", "this param is a string", []any{"foo"}),
784 | },
785 | in: map[string]any{
786 | "my_string": "bar",
787 | },
788 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "bar"}},
789 | },
790 | {
791 | name: "string with escape backticks",
792 | params: tools.Parameters{
793 | tools.NewStringParameterWithEscape("my_string", "this param is a string", "backticks"),
794 | },
795 | in: map[string]any{
796 | "my_string": "foo",
797 | },
798 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "`foo`"}},
799 | },
800 | {
801 | name: "string with escape double quotes",
802 | params: tools.Parameters{
803 | tools.NewStringParameterWithEscape("my_string", "this param is a string", "double-quotes"),
804 | },
805 | in: map[string]any{
806 | "my_string": "foo",
807 | },
808 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: `"foo"`}},
809 | },
810 | {
811 | name: "string with escape single quotes",
812 | params: tools.Parameters{
813 | tools.NewStringParameterWithEscape("my_string", "this param is a string", "single-quotes"),
814 | },
815 | in: map[string]any{
816 | "my_string": "foo",
817 | },
818 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: `'foo'`}},
819 | },
820 | {
821 | name: "string with escape square brackets",
822 | params: tools.Parameters{
823 | tools.NewStringParameterWithEscape("my_string", "this param is a string", "square-brackets"),
824 | },
825 | in: map[string]any{
826 | "my_string": "foo",
827 | },
828 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "[foo]"}},
829 | },
830 | {
831 | name: "int",
832 | params: tools.Parameters{
833 | tools.NewIntParameter("my_int", "this param is an int"),
834 | },
835 | in: map[string]any{
836 | "my_int": 100,
837 | },
838 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 100}},
839 | },
840 | {
841 | name: "not int",
842 | params: tools.Parameters{
843 | tools.NewIntParameter("my_int", "this param is an int"),
844 | },
845 | in: map[string]any{
846 | "my_int": 14.5,
847 | },
848 | },
849 | {
850 | name: "not int (big)",
851 | params: tools.Parameters{
852 | tools.NewIntParameter("my_int", "this param is an int"),
853 | },
854 | in: map[string]any{
855 | "my_int": math.MaxInt64,
856 | },
857 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: math.MaxInt64}},
858 | },
859 | {
860 | name: "int allowed",
861 | params: tools.Parameters{
862 | tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{1}),
863 | },
864 | in: map[string]any{
865 | "my_int": 1,
866 | },
867 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 1}},
868 | },
869 | {
870 | name: "int allowed regex",
871 | params: tools.Parameters{
872 | tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{"^\\d{2}$"}),
873 | },
874 | in: map[string]any{
875 | "my_int": 10,
876 | },
877 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 10}},
878 | },
879 | {
880 | name: "int not allowed",
881 | params: tools.Parameters{
882 | tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{1}),
883 | },
884 | in: map[string]any{
885 | "my_int": 2,
886 | },
887 | },
888 | {
889 | name: "int not allowed regex",
890 | params: tools.Parameters{
891 | tools.NewIntParameterWithAllowedValues("my_int", "this param is an int", []any{"^\\d{2}$"}),
892 | },
893 | in: map[string]any{
894 | "my_int": 100,
895 | },
896 | },
897 | {
898 | name: "int excluded",
899 | params: tools.Parameters{
900 | tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{1}),
901 | },
902 | in: map[string]any{
903 | "my_int": 1,
904 | },
905 | },
906 | {
907 | name: "int excluded regex",
908 | params: tools.Parameters{
909 | tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{"^\\d{2}$"}),
910 | },
911 | in: map[string]any{
912 | "my_int": 10,
913 | },
914 | },
915 | {
916 | name: "int not excluded",
917 | params: tools.Parameters{
918 | tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{1}),
919 | },
920 | in: map[string]any{
921 | "my_int": 2,
922 | },
923 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 2}},
924 | },
925 | {
926 | name: "int not excluded regex",
927 | params: tools.Parameters{
928 | tools.NewIntParameterWithExcludedValues("my_int", "this param is an int", []any{"^\\d{2}$"}),
929 | },
930 | in: map[string]any{
931 | "my_int": 2,
932 | },
933 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 2}},
934 | },
935 | {
936 | name: "int minValue",
937 | params: tools.Parameters{
938 | tools.NewIntParameterWithRange("my_int", "this param is an int", &intValue, nil),
939 | },
940 | in: map[string]any{
941 | "my_int": 3,
942 | },
943 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 3}},
944 | },
945 | {
946 | name: "int minValue disallow",
947 | params: tools.Parameters{
948 | tools.NewIntParameterWithRange("my_int", "this param is an int", &intValue, nil),
949 | },
950 | in: map[string]any{
951 | "my_int": 1,
952 | },
953 | },
954 | {
955 | name: "int maxValue",
956 | params: tools.Parameters{
957 | tools.NewIntParameterWithRange("my_int", "this param is an int", nil, &intValue),
958 | },
959 | in: map[string]any{
960 | "my_int": 1,
961 | },
962 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 1}},
963 | },
964 | {
965 | name: "int maxValue disallow",
966 | params: tools.Parameters{
967 | tools.NewIntParameterWithRange("my_int", "this param is an int", nil, &intValue),
968 | },
969 | in: map[string]any{
970 | "my_int": 3,
971 | },
972 | },
973 | {
974 | name: "float",
975 | params: tools.Parameters{
976 | tools.NewFloatParameter("my_float", "this param is a float"),
977 | },
978 | in: map[string]any{
979 | "my_float": 1.5,
980 | },
981 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.5}},
982 | },
983 | {
984 | name: "not float",
985 | params: tools.Parameters{
986 | tools.NewFloatParameter("my_float", "this param is a float"),
987 | },
988 | in: map[string]any{
989 | "my_float": true,
990 | },
991 | },
992 | {
993 | name: "float allowed",
994 | params: tools.Parameters{
995 | tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{1.1}),
996 | },
997 | in: map[string]any{
998 | "my_float": 1.1,
999 | },
1000 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.1}},
1001 | },
1002 | {
1003 | name: "float allowed regex",
1004 | params: tools.Parameters{
1005 | tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}),
1006 | },
1007 | in: map[string]any{
1008 | "my_float": 0.99,
1009 | },
1010 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 0.99}},
1011 | },
1012 | {
1013 | name: "float not allowed",
1014 | params: tools.Parameters{
1015 | tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{1.1}),
1016 | },
1017 | in: map[string]any{
1018 | "my_float": 1.2,
1019 | },
1020 | },
1021 | {
1022 | name: "float not allowed regex",
1023 | params: tools.Parameters{
1024 | tools.NewFloatParameterWithAllowedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}),
1025 | },
1026 | in: map[string]any{
1027 | "my_float": 1.99,
1028 | },
1029 | },
1030 | {
1031 | name: "float excluded",
1032 | params: tools.Parameters{
1033 | tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{1.1}),
1034 | },
1035 | in: map[string]any{
1036 | "my_float": 1.1,
1037 | },
1038 | },
1039 | {
1040 | name: "float excluded regex",
1041 | params: tools.Parameters{
1042 | tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}),
1043 | },
1044 | in: map[string]any{
1045 | "my_float": 0.99,
1046 | },
1047 | },
1048 | {
1049 | name: "float not excluded",
1050 | params: tools.Parameters{
1051 | tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{1.1}),
1052 | },
1053 | in: map[string]any{
1054 | "my_float": 1.2,
1055 | },
1056 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.2}},
1057 | },
1058 | {
1059 | name: "float not excluded regex",
1060 | params: tools.Parameters{
1061 | tools.NewFloatParameterWithExcludedValues("my_float", "this param is a float", []any{"^0\\.\\d+$"}),
1062 | },
1063 | in: map[string]any{
1064 | "my_float": 1.99,
1065 | },
1066 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.99}},
1067 | },
1068 |
1069 | {
1070 | name: "float minValue",
1071 | params: tools.Parameters{
1072 | tools.NewFloatParameterWithRange("my_float", "this param is a float", &floatValue, nil),
1073 | },
1074 | in: map[string]any{
1075 | "my_float": 1.8,
1076 | },
1077 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.8}},
1078 | },
1079 | {
1080 | name: "float minValue disallow",
1081 | params: tools.Parameters{
1082 | tools.NewFloatParameterWithRange("my_float", "this param is a float", &floatValue, nil),
1083 | },
1084 | in: map[string]any{
1085 | "my_float": 1.2,
1086 | },
1087 | },
1088 | {
1089 | name: "float maxValue",
1090 | params: tools.Parameters{
1091 | tools.NewFloatParameterWithRange("my_float", "this param is a float", nil, &floatValue),
1092 | },
1093 | in: map[string]any{
1094 | "my_float": 1.2,
1095 | },
1096 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.2}},
1097 | },
1098 | {
1099 | name: "float maxValue disallow",
1100 | params: tools.Parameters{
1101 | tools.NewFloatParameterWithRange("my_float", "this param is a float", nil, &floatValue),
1102 | },
1103 | in: map[string]any{
1104 | "my_float": 1.8,
1105 | },
1106 | },
1107 | {
1108 | name: "bool",
1109 | params: tools.Parameters{
1110 | tools.NewBooleanParameter("my_bool", "this param is a bool"),
1111 | },
1112 | in: map[string]any{
1113 | "my_bool": true,
1114 | },
1115 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}},
1116 | },
1117 | {
1118 | name: "not bool",
1119 | params: tools.Parameters{
1120 | tools.NewBooleanParameter("my_bool", "this param is a bool"),
1121 | },
1122 | in: map[string]any{
1123 | "my_bool": 1.5,
1124 | },
1125 | },
1126 | {
1127 | name: "bool allowed",
1128 | params: tools.Parameters{
1129 | tools.NewBooleanParameterWithAllowedValues("my_bool", "this param is a bool", []any{false}),
1130 | },
1131 | in: map[string]any{
1132 | "my_bool": false,
1133 | },
1134 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: false}},
1135 | },
1136 | {
1137 | name: "bool not allowed",
1138 | params: tools.Parameters{
1139 | tools.NewBooleanParameterWithAllowedValues("my_bool", "this param is a bool", []any{false}),
1140 | },
1141 | in: map[string]any{
1142 | "my_bool": true,
1143 | },
1144 | },
1145 | {
1146 | name: "bool excluded",
1147 | params: tools.Parameters{
1148 | tools.NewBooleanParameterWithExcludedValues("my_bool", "this param is a bool", []any{true}),
1149 | },
1150 | in: map[string]any{
1151 | "my_bool": true,
1152 | },
1153 | },
1154 | {
1155 | name: "bool not excluded",
1156 | params: tools.Parameters{
1157 | tools.NewBooleanParameterWithExcludedValues("my_bool", "this param is a bool", []any{false}),
1158 | },
1159 | in: map[string]any{
1160 | "my_bool": true,
1161 | },
1162 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}},
1163 | },
1164 | {
1165 | name: "string default",
1166 | params: tools.Parameters{
1167 | tools.NewStringParameterWithDefault("my_string", "foo", "this param is a string"),
1168 | },
1169 | in: map[string]any{},
1170 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "foo"}},
1171 | },
1172 | {
1173 | name: "int default",
1174 | params: tools.Parameters{
1175 | tools.NewIntParameterWithDefault("my_int", 100, "this param is an int"),
1176 | },
1177 | in: map[string]any{},
1178 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 100}},
1179 | },
1180 | {
1181 | name: "int (big)",
1182 | params: tools.Parameters{
1183 | tools.NewIntParameterWithDefault("my_big_int", math.MaxInt64, "this param is an int"),
1184 | },
1185 | in: map[string]any{},
1186 | want: tools.ParamValues{tools.ParamValue{Name: "my_big_int", Value: math.MaxInt64}},
1187 | },
1188 | {
1189 | name: "float default",
1190 | params: tools.Parameters{
1191 | tools.NewFloatParameterWithDefault("my_float", 1.1, "this param is a float"),
1192 | },
1193 | in: map[string]any{},
1194 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 1.1}},
1195 | },
1196 | {
1197 | name: "bool default",
1198 | params: tools.Parameters{
1199 | tools.NewBooleanParameterWithDefault("my_bool", true, "this param is a bool"),
1200 | },
1201 | in: map[string]any{},
1202 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}},
1203 | },
1204 | {
1205 | name: "string not required",
1206 | params: tools.Parameters{
1207 | tools.NewStringParameterWithRequired("my_string", "this param is a string", false),
1208 | },
1209 | in: map[string]any{},
1210 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: nil}},
1211 | },
1212 | {
1213 | name: "int not required",
1214 | params: tools.Parameters{
1215 | tools.NewIntParameterWithRequired("my_int", "this param is an int", false),
1216 | },
1217 | in: map[string]any{},
1218 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: nil}},
1219 | },
1220 | {
1221 | name: "float not required",
1222 | params: tools.Parameters{
1223 | tools.NewFloatParameterWithRequired("my_float", "this param is a float", false),
1224 | },
1225 | in: map[string]any{},
1226 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: nil}},
1227 | },
1228 | {
1229 | name: "bool not required",
1230 | params: tools.Parameters{
1231 | tools.NewBooleanParameterWithRequired("my_bool", "this param is a bool", false),
1232 | },
1233 | in: map[string]any{},
1234 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: nil}},
1235 | },
1236 | {
1237 | name: "array with string escape",
1238 | params: tools.Parameters{
1239 | tools.NewArrayParameter("my_array", "an array", tools.NewStringParameterWithEscape("my_string", "string item", "backticks")),
1240 | },
1241 | in: map[string]any{
1242 | "my_array": []string{"val1", "val2"},
1243 | },
1244 | want: tools.ParamValues{tools.ParamValue{Name: "my_array", Value: []any{string("`val1`"), string("`val2`")}}},
1245 | },
1246 | {
1247 | name: "map",
1248 | params: tools.Parameters{
1249 | tools.NewMapParameter("my_map", "a map", "string"),
1250 | },
1251 | in: map[string]any{
1252 | "my_map": map[string]any{"key1": "val1", "key2": "val2"},
1253 | },
1254 | want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"key1": "val1", "key2": "val2"}}},
1255 | },
1256 | {
1257 | name: "generic map",
1258 | params: tools.Parameters{
1259 | tools.NewMapParameter("my_map_generic_type", "a generic map", ""),
1260 | },
1261 | in: map[string]any{
1262 | "my_map_generic_type": map[string]any{"key1": "val1", "key2": 123, "key3": true},
1263 | },
1264 | want: tools.ParamValues{tools.ParamValue{Name: "my_map_generic_type", Value: map[string]any{"key1": "val1", "key2": int64(123), "key3": true}}},
1265 | },
1266 | {
1267 | name: "not map (value type mismatch)",
1268 | params: tools.Parameters{
1269 | tools.NewMapParameter("my_map", "a map", "string"),
1270 | },
1271 | in: map[string]any{
1272 | "my_map": map[string]any{"key1": 123},
1273 | },
1274 | },
1275 | {
1276 | name: "map default",
1277 | params: tools.Parameters{
1278 | tools.NewMapParameterWithDefault("my_map_default", map[string]any{"default_key": "default_val"}, "a map", "string"),
1279 | },
1280 | in: map[string]any{},
1281 | want: tools.ParamValues{tools.ParamValue{Name: "my_map_default", Value: map[string]any{"default_key": "default_val"}}},
1282 | },
1283 | {
1284 | name: "map not required",
1285 | params: tools.Parameters{
1286 | tools.NewMapParameterWithRequired("my_map_not_required", "a map", false, "string"),
1287 | },
1288 | in: map[string]any{},
1289 | want: tools.ParamValues{tools.ParamValue{Name: "my_map_not_required", Value: nil}},
1290 | },
1291 | {
1292 | name: "map allowed",
1293 | params: tools.Parameters{
1294 | tools.NewMapParameterWithAllowedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
1295 | },
1296 | in: map[string]any{
1297 | "my_map": map[string]any{"key1": "val1"},
1298 | },
1299 | want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"key1": "val1"}}},
1300 | },
1301 | {
1302 | name: "map not allowed",
1303 | params: tools.Parameters{
1304 | tools.NewMapParameterWithAllowedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
1305 | },
1306 | in: map[string]any{
1307 | "my_map": map[string]any{"key1": "val2"},
1308 | },
1309 | },
1310 | {
1311 | name: "map excluded",
1312 | params: tools.Parameters{
1313 | tools.NewMapParameterWithExcludedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
1314 | },
1315 | in: map[string]any{
1316 | "my_map": map[string]any{"key1": "val1"},
1317 | },
1318 | },
1319 | {
1320 | name: "map not excluded",
1321 | params: tools.Parameters{
1322 | tools.NewMapParameterWithExcludedValues("my_map", "a map", []any{map[string]any{"key1": "val1"}}, "string"),
1323 | },
1324 | in: map[string]any{
1325 | "my_map": map[string]any{"key1": "val2"},
1326 | },
1327 | want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"key1": "val2"}}},
1328 | },
1329 | }
1330 | for _, tc := range tcs {
1331 | t.Run(tc.name, func(t *testing.T) {
1332 | // parse map to bytes
1333 | data, err := json.Marshal(tc.in)
1334 | if err != nil {
1335 | t.Fatalf("unable to marshal input to yaml: %s", err)
1336 | }
1337 | // parse bytes to object
1338 | var m map[string]any
1339 |
1340 | d := json.NewDecoder(bytes.NewReader(data))
1341 | d.UseNumber()
1342 | err = d.Decode(&m)
1343 | if err != nil {
1344 | t.Fatalf("unable to unmarshal: %s", err)
1345 | }
1346 |
1347 | wantErr := len(tc.want) == 0 // error is expected if no items in want
1348 | gotAll, err := tools.ParseParams(tc.params, m, make(map[string]map[string]any))
1349 | if err != nil {
1350 | if wantErr {
1351 | return
1352 | }
1353 | t.Fatalf("unexpected error from ParseParams: %s", err)
1354 | }
1355 | if wantErr {
1356 | t.Fatalf("expected error but Param parsed successfully: %s", gotAll)
1357 | }
1358 |
1359 | // Use cmp.Diff for robust comparison
1360 | if diff := cmp.Diff(tc.want, gotAll); diff != "" {
1361 | t.Fatalf("ParseParams() mismatch (-want +got):\n%s", diff)
1362 | }
1363 | })
1364 | }
1365 | }
1366 |
1367 | func TestAuthParametersParse(t *testing.T) {
1368 | authServices := []tools.ParamAuthService{
1369 | {
1370 | Name: "my-google-auth-service",
1371 | Field: "auth_field",
1372 | },
1373 | {
1374 | Name: "other-auth-service",
1375 | Field: "other_auth_field",
1376 | }}
1377 | tcs := []struct {
1378 | name string
1379 | params tools.Parameters
1380 | in map[string]any
1381 | claimsMap map[string]map[string]any
1382 | want tools.ParamValues
1383 | }{
1384 | {
1385 | name: "string",
1386 | params: tools.Parameters{
1387 | tools.NewStringParameterWithAuth("my_string", "this param is a string", authServices),
1388 | },
1389 | in: map[string]any{
1390 | "my_string": "hello world",
1391 | },
1392 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"auth_field": "hello"}},
1393 | want: tools.ParamValues{tools.ParamValue{Name: "my_string", Value: "hello"}},
1394 | },
1395 | {
1396 | name: "not string",
1397 | params: tools.Parameters{
1398 | tools.NewStringParameterWithAuth("my_string", "this param is a string", authServices),
1399 | },
1400 | in: map[string]any{
1401 | "my_string": 4,
1402 | },
1403 | claimsMap: map[string]map[string]any{},
1404 | },
1405 | {
1406 | name: "int",
1407 | params: tools.Parameters{
1408 | tools.NewIntParameterWithAuth("my_int", "this param is an int", authServices),
1409 | },
1410 | in: map[string]any{
1411 | "my_int": 100,
1412 | },
1413 | claimsMap: map[string]map[string]any{"other-auth-service": {"other_auth_field": 120}},
1414 | want: tools.ParamValues{tools.ParamValue{Name: "my_int", Value: 120}},
1415 | },
1416 | {
1417 | name: "not int",
1418 | params: tools.Parameters{
1419 | tools.NewIntParameterWithAuth("my_int", "this param is an int", authServices),
1420 | },
1421 | in: map[string]any{
1422 | "my_int": 14.5,
1423 | },
1424 | claimsMap: map[string]map[string]any{},
1425 | },
1426 | {
1427 | name: "float",
1428 | params: tools.Parameters{
1429 | tools.NewFloatParameterWithAuth("my_float", "this param is a float", authServices),
1430 | },
1431 | in: map[string]any{
1432 | "my_float": 1.5,
1433 | },
1434 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"auth_field": 2.1}},
1435 | want: tools.ParamValues{tools.ParamValue{Name: "my_float", Value: 2.1}},
1436 | },
1437 | {
1438 | name: "not float",
1439 | params: tools.Parameters{
1440 | tools.NewFloatParameterWithAuth("my_float", "this param is a float", authServices),
1441 | },
1442 | in: map[string]any{
1443 | "my_float": true,
1444 | },
1445 | claimsMap: map[string]map[string]any{},
1446 | },
1447 | {
1448 | name: "bool",
1449 | params: tools.Parameters{
1450 | tools.NewBooleanParameterWithAuth("my_bool", "this param is a bool", authServices),
1451 | },
1452 | in: map[string]any{
1453 | "my_bool": true,
1454 | },
1455 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"auth_field": false}},
1456 | want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: false}},
1457 | },
1458 | {
1459 | name: "not bool",
1460 | params: tools.Parameters{
1461 | tools.NewBooleanParameterWithAuth("my_bool", "this param is a bool", authServices),
1462 | },
1463 | in: map[string]any{
1464 | "my_bool": 1.5,
1465 | },
1466 | claimsMap: map[string]map[string]any{},
1467 | },
1468 | {
1469 | name: "username",
1470 | params: tools.Parameters{
1471 | tools.NewStringParameterWithAuth("username", "username string", authServices),
1472 | },
1473 | in: map[string]any{
1474 | "username": "Violet",
1475 | },
1476 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"auth_field": "Alice"}},
1477 | want: tools.ParamValues{tools.ParamValue{Name: "username", Value: "Alice"}},
1478 | },
1479 | {
1480 | name: "expect claim error",
1481 | params: tools.Parameters{
1482 | tools.NewStringParameterWithAuth("username", "username string", authServices),
1483 | },
1484 | in: map[string]any{
1485 | "username": "Violet",
1486 | },
1487 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"not_an_auth_field": "Alice"}},
1488 | },
1489 | {
1490 | name: "map",
1491 | params: tools.Parameters{
1492 | tools.NewMapParameterWithAuth("my_map", "a map", "string", authServices),
1493 | },
1494 | in: map[string]any{"my_map": map[string]any{"key1": "val1"}},
1495 | claimsMap: map[string]map[string]any{"my-google-auth-service": {"auth_field": map[string]any{"authed_key": "authed_val"}}},
1496 | want: tools.ParamValues{tools.ParamValue{Name: "my_map", Value: map[string]any{"authed_key": "authed_val"}}},
1497 | },
1498 | }
1499 | for _, tc := range tcs {
1500 | t.Run(tc.name, func(t *testing.T) {
1501 | // parse map to bytes
1502 | data, err := json.Marshal(tc.in)
1503 | if err != nil {
1504 | t.Fatalf("unable to marshal input to yaml: %s", err)
1505 | }
1506 | // parse bytes to object
1507 | var m map[string]any
1508 | d := json.NewDecoder(bytes.NewReader(data))
1509 | d.UseNumber()
1510 | err = d.Decode(&m)
1511 | if err != nil {
1512 | t.Fatalf("unable to unmarshal: %s", err)
1513 | }
1514 |
1515 | gotAll, err := tools.ParseParams(tc.params, m, tc.claimsMap)
1516 | if err != nil {
1517 | if len(tc.want) == 0 {
1518 | // error is expected if no items in want
1519 | return
1520 | }
1521 | t.Fatalf("unexpected error from ParseParams: %s", err)
1522 | }
1523 |
1524 | if diff := cmp.Diff(tc.want, gotAll); diff != "" {
1525 | t.Fatalf("ParseParams() mismatch (-want +got):\n%s", diff)
1526 | }
1527 | })
1528 | }
1529 | }
1530 |
1531 | func TestParamValues(t *testing.T) {
1532 | tcs := []struct {
1533 | name string
1534 | in tools.ParamValues
1535 | wantSlice []any
1536 | wantMap map[string]interface{}
1537 | wantMapOrdered map[string]interface{}
1538 | wantMapWithDollar map[string]interface{}
1539 | }{
1540 | {
1541 | name: "string",
1542 | in: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}, tools.ParamValue{Name: "my_string", Value: "hello world"}},
1543 | wantSlice: []any{true, "hello world"},
1544 | wantMap: map[string]interface{}{"my_bool": true, "my_string": "hello world"},
1545 | wantMapOrdered: map[string]interface{}{"p1": true, "p2": "hello world"},
1546 | wantMapWithDollar: map[string]interface{}{
1547 | "$my_bool": true,
1548 | "$my_string": "hello world",
1549 | },
1550 | },
1551 | }
1552 | for _, tc := range tcs {
1553 | t.Run(tc.name, func(t *testing.T) {
1554 | gotSlice := tc.in.AsSlice()
1555 | gotMap := tc.in.AsMap()
1556 | gotMapOrdered := tc.in.AsMapByOrderedKeys()
1557 | gotMapWithDollar := tc.in.AsMapWithDollarPrefix()
1558 |
1559 | for i, got := range gotSlice {
1560 | want := tc.wantSlice[i]
1561 | if got != want {
1562 | t.Fatalf("unexpected value: got %q, want %q", got, want)
1563 | }
1564 | }
1565 | for i, got := range gotMap {
1566 | want := tc.wantMap[i]
1567 | if got != want {
1568 | t.Fatalf("unexpected value: got %q, want %q", got, want)
1569 | }
1570 | }
1571 | for i, got := range gotMapOrdered {
1572 | want := tc.wantMapOrdered[i]
1573 | if got != want {
1574 | t.Fatalf("unexpected value: got %q, want %q", got, want)
1575 | }
1576 | }
1577 | for key, got := range gotMapWithDollar {
1578 | want := tc.wantMapWithDollar[key]
1579 | if got != want {
1580 | t.Fatalf("unexpected value in AsMapWithDollarPrefix: got %q, want %q", got, want)
1581 | }
1582 | }
1583 | })
1584 | }
1585 | }
1586 |
1587 | func TestParamManifest(t *testing.T) {
1588 | tcs := []struct {
1589 | name string
1590 | in tools.Parameter
1591 | want tools.ParameterManifest
1592 | }{
1593 | {
1594 | name: "string",
1595 | in: tools.NewStringParameter("foo-string", "bar"),
1596 | want: tools.ParameterManifest{Name: "foo-string", Type: "string", Required: true, Description: "bar", AuthServices: []string{}},
1597 | },
1598 | {
1599 | name: "int",
1600 | in: tools.NewIntParameter("foo-int", "bar"),
1601 | want: tools.ParameterManifest{Name: "foo-int", Type: "integer", Required: true, Description: "bar", AuthServices: []string{}},
1602 | },
1603 | {
1604 | name: "float",
1605 | in: tools.NewFloatParameter("foo-float", "bar"),
1606 | want: tools.ParameterManifest{Name: "foo-float", Type: "float", Required: true, Description: "bar", AuthServices: []string{}},
1607 | },
1608 | {
1609 | name: "boolean",
1610 | in: tools.NewBooleanParameter("foo-bool", "bar"),
1611 | want: tools.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: true, Description: "bar", AuthServices: []string{}},
1612 | },
1613 | {
1614 | name: "array",
1615 | in: tools.NewArrayParameter("foo-array", "bar", tools.NewStringParameter("foo-string", "bar")),
1616 | want: tools.ParameterManifest{
1617 | Name: "foo-array",
1618 | Type: "array",
1619 | Required: true,
1620 | Description: "bar",
1621 | AuthServices: []string{},
1622 | Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: true, Description: "bar", AuthServices: []string{}},
1623 | },
1624 | },
1625 | {
1626 | name: "string default",
1627 | in: tools.NewStringParameterWithDefault("foo-string", "foo", "bar"),
1628 | want: tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
1629 | },
1630 | {
1631 | name: "int default",
1632 | in: tools.NewIntParameterWithDefault("foo-int", 1, "bar"),
1633 | want: tools.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", AuthServices: []string{}},
1634 | },
1635 | {
1636 | name: "float default",
1637 | in: tools.NewFloatParameterWithDefault("foo-float", 1.1, "bar"),
1638 | want: tools.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", AuthServices: []string{}},
1639 | },
1640 | {
1641 | name: "boolean default",
1642 | in: tools.NewBooleanParameterWithDefault("foo-bool", true, "bar"),
1643 | want: tools.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", AuthServices: []string{}},
1644 | },
1645 | {
1646 | name: "array default",
1647 | in: tools.NewArrayParameterWithDefault("foo-array", []any{"foo", "bar"}, "bar", tools.NewStringParameter("foo-string", "bar")),
1648 | want: tools.ParameterManifest{
1649 | Name: "foo-array",
1650 | Type: "array",
1651 | Required: false,
1652 | Description: "bar",
1653 | AuthServices: []string{},
1654 | Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
1655 | },
1656 | },
1657 | {
1658 | name: "string not required",
1659 | in: tools.NewStringParameterWithRequired("foo-string", "bar", false),
1660 | want: tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
1661 | },
1662 | {
1663 | name: "int not required",
1664 | in: tools.NewIntParameterWithRequired("foo-int", "bar", false),
1665 | want: tools.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", AuthServices: []string{}},
1666 | },
1667 | {
1668 | name: "float not required",
1669 | in: tools.NewFloatParameterWithRequired("foo-float", "bar", false),
1670 | want: tools.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", AuthServices: []string{}},
1671 | },
1672 | {
1673 | name: "boolean not required",
1674 | in: tools.NewBooleanParameterWithRequired("foo-bool", "bar", false),
1675 | want: tools.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", AuthServices: []string{}},
1676 | },
1677 | {
1678 | name: "array not required",
1679 | in: tools.NewArrayParameterWithRequired("foo-array", "bar", false, tools.NewStringParameter("foo-string", "bar")),
1680 | want: tools.ParameterManifest{
1681 | Name: "foo-array",
1682 | Type: "array",
1683 | Required: false,
1684 | Description: "bar",
1685 | AuthServices: []string{},
1686 | Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
1687 | },
1688 | },
1689 | {
1690 | name: "map with string values",
1691 | in: tools.NewMapParameter("foo-map", "bar", "string"),
1692 | want: tools.ParameterManifest{
1693 | Name: "foo-map",
1694 | Type: "object",
1695 | Required: true,
1696 | Description: "bar",
1697 | AuthServices: []string{},
1698 | AdditionalProperties: map[string]any{"type": "string"},
1699 | },
1700 | },
1701 | {
1702 | name: "map not required",
1703 | in: tools.NewMapParameterWithRequired("foo-map", "bar", false, "string"),
1704 | want: tools.ParameterManifest{
1705 | Name: "foo-map",
1706 | Type: "object",
1707 | Required: false,
1708 | Description: "bar",
1709 | AuthServices: []string{},
1710 | AdditionalProperties: map[string]any{"type": "string"},
1711 | },
1712 | },
1713 | {
1714 | name: "generic map (additionalProperties true)",
1715 | in: tools.NewMapParameter("foo-map", "bar", ""),
1716 | want: tools.ParameterManifest{
1717 | Name: "foo-map",
1718 | Type: "object",
1719 | Required: true,
1720 | Description: "bar",
1721 | AuthServices: []string{},
1722 | AdditionalProperties: true,
1723 | },
1724 | },
1725 | }
1726 | for _, tc := range tcs {
1727 | t.Run(tc.name, func(t *testing.T) {
1728 | got := tc.in.Manifest()
1729 | if diff := cmp.Diff(tc.want, got); diff != "" {
1730 | t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
1731 | }
1732 | })
1733 | }
1734 | }
1735 |
1736 | func TestParamMcpManifest(t *testing.T) {
1737 | tcs := []struct {
1738 | name string
1739 | in tools.Parameter
1740 | want tools.ParameterMcpManifest
1741 | wantAuthParam []string
1742 | }{
1743 | {
1744 | name: "string",
1745 | in: tools.NewStringParameter("foo-string", "bar"),
1746 | want: tools.ParameterMcpManifest{Type: "string", Description: "bar"},
1747 | wantAuthParam: []string{},
1748 | },
1749 | {
1750 | name: "int",
1751 | in: tools.NewIntParameter("foo-int", "bar"),
1752 | want: tools.ParameterMcpManifest{Type: "integer", Description: "bar"},
1753 | wantAuthParam: []string{},
1754 | },
1755 | {
1756 | name: "float",
1757 | in: tools.NewFloatParameter("foo-float", "bar"),
1758 | want: tools.ParameterMcpManifest{Type: "number", Description: "bar"},
1759 | wantAuthParam: []string{},
1760 | },
1761 | {
1762 | name: "boolean",
1763 | in: tools.NewBooleanParameter("foo-bool", "bar"),
1764 | want: tools.ParameterMcpManifest{Type: "boolean", Description: "bar"},
1765 | wantAuthParam: []string{},
1766 | },
1767 | {
1768 | name: "array",
1769 | in: tools.NewArrayParameter("foo-array", "bar", tools.NewStringParameter("foo-string", "bar")),
1770 | want: tools.ParameterMcpManifest{
1771 | Type: "array",
1772 | Description: "bar",
1773 | Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
1774 | },
1775 | wantAuthParam: []string{},
1776 | },
1777 |
1778 | {
1779 | name: "map with string values",
1780 | in: tools.NewMapParameter("foo-map", "bar", "string"),
1781 | want: tools.ParameterMcpManifest{
1782 | Type: "object",
1783 | Description: "bar",
1784 | AdditionalProperties: map[string]any{"type": "string"},
1785 | },
1786 | wantAuthParam: []string{},
1787 | },
1788 | {
1789 | name: "generic map (additionalProperties true)",
1790 | in: tools.NewMapParameter("foo-map", "bar", ""),
1791 | want: tools.ParameterMcpManifest{
1792 | Type: "object",
1793 | Description: "bar",
1794 | AdditionalProperties: true,
1795 | },
1796 | wantAuthParam: []string{},
1797 | },
1798 | }
1799 | for _, tc := range tcs {
1800 | t.Run(tc.name, func(t *testing.T) {
1801 | got, gotAuthParam := tc.in.McpManifest()
1802 | if diff := cmp.Diff(tc.want, got); diff != "" {
1803 | t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
1804 | }
1805 | slices.Sort(gotAuthParam)
1806 | if !reflect.DeepEqual(gotAuthParam, tc.wantAuthParam) {
1807 | t.Fatalf("unexpected auth param list: got %s, want %s", gotAuthParam, tc.wantAuthParam)
1808 | }
1809 | })
1810 | }
1811 | }
1812 |
1813 | func TestMcpManifest(t *testing.T) {
1814 | authServices := []tools.ParamAuthService{
1815 | {
1816 | Name: "my-google-auth-service",
1817 | Field: "auth_field",
1818 | },
1819 | {
1820 | Name: "other-auth-service",
1821 | Field: "other_auth_field",
1822 | }}
1823 | tcs := []struct {
1824 | name string
1825 | in tools.Parameters
1826 | wantSchema tools.McpToolsSchema
1827 | wantAuthParam map[string][]string
1828 | }{
1829 | {
1830 | name: "all types",
1831 | in: tools.Parameters{
1832 | tools.NewStringParameterWithDefault("foo-string", "foo", "bar"),
1833 | tools.NewStringParameter("foo-string2", "bar"),
1834 | tools.NewStringParameterWithAuth("foo-string3-auth", "bar", authServices),
1835 | tools.NewIntParameter("foo-int2", "bar"),
1836 | tools.NewFloatParameter("foo-float", "bar"),
1837 | tools.NewArrayParameter("foo-array2", "bar", tools.NewStringParameter("foo-string", "bar")),
1838 | tools.NewMapParameter("foo-map-int", "a map of ints", "integer"),
1839 | tools.NewMapParameter("foo-map-any", "a map of any", ""),
1840 | },
1841 | wantSchema: tools.McpToolsSchema{
1842 | Type: "object",
1843 | Properties: map[string]tools.ParameterMcpManifest{
1844 | "foo-string": {Type: "string", Description: "bar"},
1845 | "foo-string2": {Type: "string", Description: "bar"},
1846 | "foo-string3-auth": {Type: "string", Description: "bar"},
1847 | "foo-int2": {Type: "integer", Description: "bar"},
1848 | "foo-float": {Type: "number", Description: "bar"},
1849 | "foo-array2": {
1850 | Type: "array",
1851 | Description: "bar",
1852 | Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
1853 | },
1854 | "foo-map-int": {
1855 | Type: "object",
1856 | Description: "a map of ints",
1857 | AdditionalProperties: map[string]any{"type": "integer"},
1858 | },
1859 | "foo-map-any": {
1860 | Type: "object",
1861 | Description: "a map of any",
1862 | AdditionalProperties: true,
1863 | },
1864 | },
1865 | Required: []string{"foo-string2", "foo-string3-auth", "foo-int2", "foo-float", "foo-array2", "foo-map-int", "foo-map-any"},
1866 | },
1867 | wantAuthParam: map[string][]string{
1868 | "foo-string3-auth": []string{"my-google-auth-service", "other-auth-service"},
1869 | },
1870 | },
1871 | }
1872 | for _, tc := range tcs {
1873 | t.Run(tc.name, func(t *testing.T) {
1874 | gotSchema, gotAuthParam := tc.in.McpManifest()
1875 | if diff := cmp.Diff(tc.wantSchema, gotSchema); diff != "" {
1876 | t.Fatalf("unexpected manifest (-want +got):\n%s", diff)
1877 | }
1878 | if len(gotAuthParam) != len(tc.wantAuthParam) {
1879 | t.Fatalf("got %d items in auth param map, want %d", len(gotAuthParam), len(tc.wantAuthParam))
1880 | }
1881 | for k, want := range tc.wantAuthParam {
1882 | got, ok := gotAuthParam[k]
1883 | if !ok {
1884 | t.Fatalf("missing auth param: %s", k)
1885 | }
1886 | slices.Sort(got)
1887 | if !reflect.DeepEqual(got, want) {
1888 | t.Fatalf("unexpected auth param, got %s, want %s", got, want)
1889 | }
1890 | }
1891 | })
1892 | }
1893 | }
1894 |
1895 | func TestFailParametersUnmarshal(t *testing.T) {
1896 | ctx, err := testutils.ContextWithNewLogger()
1897 | if err != nil {
1898 | t.Fatalf("unexpected error: %s", err)
1899 | }
1900 | tcs := []struct {
1901 | name string
1902 | in []map[string]any
1903 | err string
1904 | }{
1905 | {
1906 | name: "common parameter missing name",
1907 | in: []map[string]any{
1908 | {
1909 | "type": "string",
1910 | "description": "this is a param for string",
1911 | },
1912 | },
1913 | err: "unable to parse as \"string\": Key: 'CommonParameter.Name' Error:Field validation for 'Name' failed on the 'required' tag",
1914 | },
1915 | {
1916 | name: "common parameter missing type",
1917 | in: []map[string]any{
1918 | {
1919 | "name": "string",
1920 | "description": "this is a param for string",
1921 | },
1922 | },
1923 | err: "parameter is missing 'type' field",
1924 | },
1925 | {
1926 | name: "common parameter missing description",
1927 | in: []map[string]any{
1928 | {
1929 | "name": "my_string",
1930 | "type": "string",
1931 | },
1932 | },
1933 | err: "unable to parse as \"string\": Key: 'CommonParameter.Desc' Error:Field validation for 'Desc' failed on the 'required' tag",
1934 | },
1935 | {
1936 | name: "array parameter missing items",
1937 | in: []map[string]any{
1938 | {
1939 | "name": "my_array",
1940 | "type": "array",
1941 | "description": "this param is an array of strings",
1942 | },
1943 | },
1944 | err: "unable to parse as \"array\": unable to parse 'items' field: error parsing parameters: nothing to unmarshal",
1945 | },
1946 | {
1947 | name: "array parameter missing items' name",
1948 | in: []map[string]any{
1949 | {
1950 | "name": "my_array",
1951 | "type": "array",
1952 | "description": "this param is an array of strings",
1953 | "items": map[string]string{
1954 | "type": "string",
1955 | "description": "string item",
1956 | },
1957 | },
1958 | },
1959 | err: "unable to parse as \"array\": unable to parse 'items' field: unable to parse as \"string\": Key: 'CommonParameter.Name' Error:Field validation for 'Name' failed on the 'required' tag",
1960 | },
1961 | // --- MODIFIED MAP PARAMETER TEST ---
1962 | {
1963 | name: "map with invalid valueType",
1964 | in: []map[string]any{
1965 | {
1966 | "name": "my_map",
1967 | "type": "map",
1968 | "description": "this param is a map",
1969 | "valueType": "not-a-real-type",
1970 | },
1971 | },
1972 | err: "unsupported valueType \"not-a-real-type\" for map parameter",
1973 | },
1974 | }
1975 | for _, tc := range tcs {
1976 | t.Run(tc.name, func(t *testing.T) {
1977 | var got tools.Parameters
1978 | // parse map to bytes
1979 | data, err := yaml.Marshal(tc.in)
1980 | if err != nil {
1981 | t.Fatalf("unable to marshal input to yaml: %s", err)
1982 | }
1983 | // parse bytes to object
1984 | err = yaml.UnmarshalContext(ctx, data, &got)
1985 | if err == nil {
1986 | t.Fatalf("expect parsing to fail")
1987 | }
1988 | errStr := err.Error()
1989 |
1990 | if !strings.Contains(errStr, tc.err) {
1991 | t.Fatalf("unexpected error: got %q, want to contain %q", errStr, tc.err)
1992 | }
1993 | })
1994 | }
1995 | }
1996 |
1997 | // ... (Remaining test functions do not involve parameter definitions and need no changes)
1998 |
1999 | func TestConvertArrayParamToString(t *testing.T) {
2000 |
2001 | tcs := []struct {
2002 | name string
2003 | in []any
2004 | want string
2005 | }{
2006 | {
2007 | in: []any{
2008 | "id",
2009 | "name",
2010 | "location",
2011 | },
2012 | want: "id, name, location",
2013 | },
2014 | {
2015 | in: []any{
2016 | "id",
2017 | },
2018 | want: "id",
2019 | },
2020 | {
2021 | in: []any{
2022 | "id",
2023 | "5",
2024 | "false",
2025 | },
2026 | want: "id, 5, false",
2027 | },
2028 | {
2029 | in: []any{},
2030 | want: "",
2031 | },
2032 | {
2033 | in: []any{},
2034 | want: "",
2035 | },
2036 | }
2037 | for _, tc := range tcs {
2038 | t.Run(tc.name, func(t *testing.T) {
2039 | got, _ := tools.ConvertArrayParamToString(tc.in)
2040 | if diff := cmp.Diff(tc.want, got); diff != "" {
2041 | t.Fatalf("incorrect array param conversion: diff %v", diff)
2042 | }
2043 | })
2044 | }
2045 | }
2046 |
2047 | func TestFailConvertArrayParamToString(t *testing.T) {
2048 | tcs := []struct {
2049 | name string
2050 | in []any
2051 | err string
2052 | }{
2053 | {
2054 | in: []any{5, 10, 15},
2055 | err: "templateParameter only supports string arrays",
2056 | },
2057 | {
2058 | in: []any{"id", "name", 15},
2059 | err: "templateParameter only supports string arrays",
2060 | },
2061 | {
2062 | in: []any{false},
2063 | err: "templateParameter only supports string arrays",
2064 | },
2065 | {
2066 | in: []any{10, true},
2067 | err: "templateParameter only supports string arrays",
2068 | },
2069 | }
2070 | for _, tc := range tcs {
2071 | t.Run(tc.name, func(t *testing.T) {
2072 | _, err := tools.ConvertArrayParamToString(tc.in)
2073 | errStr := err.Error()
2074 | if errStr != tc.err {
2075 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
2076 | }
2077 | })
2078 | }
2079 | }
2080 |
2081 | func TestGetParams(t *testing.T) {
2082 | tcs := []struct {
2083 | name string
2084 | in map[string]any
2085 | params tools.Parameters
2086 | want tools.ParamValues
2087 | }{
2088 | {
2089 | name: "parameters to include and exclude",
2090 | params: tools.Parameters{
2091 | tools.NewStringParameter("my_string_inc", "this should be included"),
2092 | tools.NewStringParameter("my_string_inc2", "this should be included"),
2093 | },
2094 | in: map[string]any{
2095 | "my_string_inc": "hello world A",
2096 | "my_string_inc2": "hello world B",
2097 | "my_string_exc": "hello world C",
2098 | },
2099 | want: tools.ParamValues{
2100 | tools.ParamValue{Name: "my_string_inc", Value: "hello world A"},
2101 | tools.ParamValue{Name: "my_string_inc2", Value: "hello world B"},
2102 | },
2103 | },
2104 | {
2105 | name: "include all",
2106 | params: tools.Parameters{
2107 | tools.NewStringParameter("my_string_inc", "this should be included"),
2108 | },
2109 | in: map[string]any{
2110 | "my_string_inc": "hello world A",
2111 | },
2112 | want: tools.ParamValues{
2113 | tools.ParamValue{Name: "my_string_inc", Value: "hello world A"},
2114 | },
2115 | },
2116 | {
2117 | name: "exclude all",
2118 | params: tools.Parameters{},
2119 | in: map[string]any{
2120 | "my_string_exc": "hello world A",
2121 | "my_string_exc2": "hello world B",
2122 | },
2123 | want: tools.ParamValues{},
2124 | },
2125 | {
2126 | name: "empty",
2127 | params: tools.Parameters{},
2128 | in: map[string]any{},
2129 | want: tools.ParamValues{},
2130 | },
2131 | }
2132 | for _, tc := range tcs {
2133 | t.Run(tc.name, func(t *testing.T) {
2134 | got, _ := tools.GetParams(tc.params, tc.in)
2135 | if diff := cmp.Diff(tc.want, got); diff != "" {
2136 | t.Fatalf("incorrect get params: diff %v", diff)
2137 | }
2138 | })
2139 | }
2140 | }
2141 |
2142 | func TestFailGetParams(t *testing.T) {
2143 |
2144 | tcs := []struct {
2145 | name string
2146 | params tools.Parameters
2147 | in map[string]any
2148 | err string
2149 | }{
2150 | {
2151 | name: "missing the only parameter",
2152 | params: tools.Parameters{tools.NewStringParameter("my_string", "this was missing")},
2153 | in: map[string]any{},
2154 | err: "missing parameter my_string",
2155 | },
2156 | {
2157 | name: "missing one parameter of multiple",
2158 | params: tools.Parameters{
2159 | tools.NewStringParameter("my_string_inc", "this should be included"),
2160 | tools.NewStringParameter("my_string_exc", "this was missing"),
2161 | },
2162 | in: map[string]any{
2163 | "my_string_inc": "hello world A",
2164 | },
2165 | err: "missing parameter my_string_exc",
2166 | },
2167 | }
2168 | for _, tc := range tcs {
2169 | t.Run(tc.name, func(t *testing.T) {
2170 | _, err := tools.GetParams(tc.params, tc.in)
2171 | errStr := err.Error()
2172 | if errStr != tc.err {
2173 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
2174 | }
2175 | })
2176 | }
2177 | }
2178 |
2179 | func TestResolveTemplateParameters(t *testing.T) {
2180 | tcs := []struct {
2181 | name string
2182 | templateParams tools.Parameters
2183 | statement string
2184 | in map[string]any
2185 | want string
2186 | }{
2187 | {
2188 | name: "single template parameter",
2189 | templateParams: tools.Parameters{
2190 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2191 | },
2192 | statement: "SELECT * FROM {{.tableName}}",
2193 | in: map[string]any{
2194 | "tableName": "hotels",
2195 | },
2196 | want: "SELECT * FROM hotels",
2197 | },
2198 | {
2199 | name: "multiple template parameters",
2200 | templateParams: tools.Parameters{
2201 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2202 | tools.NewStringParameter("columnName", "this is a string template parameter"),
2203 | },
2204 | statement: "SELECT * FROM {{.tableName}} WHERE {{.columnName}} = 'Hilton'",
2205 | in: map[string]any{
2206 | "tableName": "hotels",
2207 | "columnName": "name",
2208 | },
2209 | want: "SELECT * FROM hotels WHERE name = 'Hilton'",
2210 | },
2211 | {
2212 | name: "standard and template parameter",
2213 | templateParams: tools.Parameters{
2214 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2215 | tools.NewStringParameter("hotelName", "this is a string parameter"),
2216 | },
2217 | statement: "SELECT * FROM {{.tableName}} WHERE name = $1",
2218 | in: map[string]any{
2219 | "tableName": "hotels",
2220 | "hotelName": "name",
2221 | },
2222 | want: "SELECT * FROM hotels WHERE name = $1",
2223 | },
2224 | {
2225 | name: "standard parameter",
2226 | templateParams: tools.Parameters{
2227 | tools.NewStringParameter("hotelName", "this is a string parameter"),
2228 | },
2229 | statement: "SELECT * FROM hotels WHERE name = $1",
2230 | in: map[string]any{
2231 | "hotelName": "hotels",
2232 | },
2233 | want: "SELECT * FROM hotels WHERE name = $1",
2234 | },
2235 | }
2236 | for _, tc := range tcs {
2237 | t.Run(tc.name, func(t *testing.T) {
2238 | got, _ := tools.ResolveTemplateParams(tc.templateParams, tc.statement, tc.in)
2239 | if diff := cmp.Diff(tc.want, got); diff != "" {
2240 | t.Fatalf("incorrect resolved template params: diff %v", diff)
2241 | }
2242 | })
2243 | }
2244 | }
2245 |
2246 | func TestFailResolveTemplateParameters(t *testing.T) {
2247 | tcs := []struct {
2248 | name string
2249 | templateParams tools.Parameters
2250 | statement string
2251 | in map[string]any
2252 | err string
2253 | }{
2254 | {
2255 | name: "wrong param name",
2256 | templateParams: tools.Parameters{
2257 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2258 | },
2259 | statement: "SELECT * FROM {{.missingParam}}",
2260 | in: map[string]any{},
2261 | err: "error getting template params missing parameter tableName",
2262 | },
2263 | {
2264 | name: "incomplete param template",
2265 | templateParams: tools.Parameters{
2266 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2267 | },
2268 | statement: "SELECT * FROM {{.tableName",
2269 | in: map[string]any{
2270 | "tableName": "hotels",
2271 | },
2272 | err: "error creating go template template: statement:1: unclosed action",
2273 | },
2274 | {
2275 | name: "undefined function",
2276 | templateParams: tools.Parameters{
2277 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2278 | },
2279 | statement: "SELECT * FROM {{json .tableName}}",
2280 | in: map[string]any{
2281 | "tableName": "hotels",
2282 | },
2283 | err: "error creating go template template: statement:1: function \"json\" not defined",
2284 | },
2285 | {
2286 | name: "undefined method",
2287 | templateParams: tools.Parameters{
2288 | tools.NewStringParameter("tableName", "this is a string template parameter"),
2289 | },
2290 | statement: "SELECT * FROM {{.tableName .wrong}}",
2291 | in: map[string]any{
2292 | "tableName": "hotels",
2293 | },
2294 | err: "error executing go template template: statement:1:16: executing \"statement\" at <.tableName>: tableName is not a method but has arguments",
2295 | },
2296 | }
2297 | for _, tc := range tcs {
2298 | t.Run(tc.name, func(t *testing.T) {
2299 | _, err := tools.ResolveTemplateParams(tc.templateParams, tc.statement, tc.in)
2300 | errStr := err.Error()
2301 | if errStr != tc.err {
2302 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
2303 | }
2304 | })
2305 | }
2306 | }
2307 |
2308 | func TestCheckParamRequired(t *testing.T) {
2309 | tcs := []struct {
2310 | name string
2311 | required bool
2312 | defaultV any
2313 | want bool
2314 | }{
2315 | {
2316 | name: "required and no default",
2317 | required: true,
2318 | defaultV: nil,
2319 | want: true,
2320 | },
2321 | {
2322 | name: "required and default",
2323 | required: true,
2324 | defaultV: "foo",
2325 | want: false,
2326 | },
2327 | {
2328 | name: "not required and no default",
2329 | required: false,
2330 | defaultV: nil,
2331 | want: false,
2332 | },
2333 | {
2334 | name: "not required and default",
2335 | required: false,
2336 | defaultV: "foo",
2337 | want: false,
2338 | },
2339 | }
2340 | for _, tc := range tcs {
2341 | t.Run(tc.name, func(t *testing.T) {
2342 | got := tools.CheckParamRequired(tc.required, tc.defaultV)
2343 | if got != tc.want {
2344 | t.Fatalf("got %v, want %v", got, tc.want)
2345 | }
2346 | })
2347 | }
2348 | }
2349 |
```