diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml index 0d41edcab..4140ba01a 100644 --- a/.github/workflows/pkg.yml +++ b/.github/workflows/pkg.yml @@ -74,5 +74,6 @@ jobs: - name: Ensure "make generate" has been run run: | make rm-mocked + make rm-builders make generate git diff --stat --exit-code diff --git a/.golangci.yml b/.golangci.yml index 547578f02..ba8fa7638 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -150,3 +150,7 @@ issues: text: "^G404:" linters: - gosec + exclude-dirs: + - third_party + # third_party didn't work, excluding where the mods are located as well... + - ../../../go/pkg/mod \ No newline at end of file diff --git a/Makefile b/Makefile index 5ae713cb1..aaaaf002d 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,11 @@ mockery: $(mockery) ## Install mockery. rm-mocked: grep -rl "^// Code generated by mockery" | grep .go$ | xargs -r rm +.PHONY: rm-builders +rm-builders: + # not sure why, but go.tmpl is matched when run in makefile but not when run in the commandline. + grep -rl "^// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli" | grep .go$ | grep -v go.tmpl$ | xargs -r rm + .PHONY: generate generate: mockery install-protoc gomods gomods -w go generate -x ./... diff --git a/go.mod b/go.mod index 94065c9cb..90bd08923 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,11 @@ module github.com/smartcontractkit/chainlink-common -go 1.21 +go 1.22 + +toolchain go1.22.5 require ( + github.com/atombender/go-jsonschema v0.16.0 github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 github.com/dominikbraun/graph v0.23.0 github.com/fxamacker/cbor/v2 v2.5.0 @@ -13,6 +16,7 @@ require ( github.com/hashicorp/consul/sdk v0.16.0 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-plugin v1.6.0 + github.com/iancoleman/strcase v0.3.0 github.com/invopop/jsonschema v0.12.0 github.com/jmoiron/sqlx v1.4.0 github.com/jonboulle/clockwork v0.4.0 @@ -26,6 +30,7 @@ require ( github.com/riferrei/srclient v0.5.4 github.com/shopspring/decimal v1.4.0 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 go.opentelemetry.io/otel v1.28.0 @@ -42,6 +47,15 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require ( + github.com/goccy/go-yaml v1.11.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/sanity-io/litter v1.5.5 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect +) + require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -49,7 +63,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -59,7 +73,7 @@ require ( github.com/hashicorp/yamux v0.1.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect diff --git a/go.sum b/go.sum index 3b3797a39..c53ab663f 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= github.com/Microsoft/hcsshim v0.9.4/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= +github.com/atombender/go-jsonschema v0.16.0 h1:1C6jMVzAQ4RZCBwGQYMEVZvjSBdKUw/7arkhHPS0ldg= +github.com/atombender/go-jsonschema v0.16.0/go.mod h1:qvHiMeC+Obu1QJTtD+rZGogD+Nn4QCztDJ0UNF8dBfs= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -29,6 +31,8 @@ github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -48,8 +52,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= @@ -59,8 +63,16 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= +github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -108,6 +120,10 @@ github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+ github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -124,6 +140,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro/v2 v2.9.7/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= @@ -140,14 +158,16 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= @@ -172,6 +192,7 @@ github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeB github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -188,6 +209,9 @@ github.com/riferrei/srclient v0.5.4 h1:dfwyR5u23QF7beuVl2WemUY2KXh5+Sc4DHKyPXBNY github.com/riferrei/srclient v0.5.4/go.mod h1:vbkLmWcgYa7JgfPvuy/+K8fTS0p1bApqadxrxi/S1MI= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= @@ -201,11 +225,16 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJ github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c h1:lIyMbTaF2H0Q71vkwZHX/Ew4KF2BxiKhqEXwF8rn+KI= github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -299,6 +328,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -322,6 +352,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/pkg/capabilities/cli/README.md b/pkg/capabilities/cli/README.md new file mode 100644 index 000000000..57864cbb8 --- /dev/null +++ b/pkg/capabilities/cli/README.md @@ -0,0 +1,24 @@ +# Capabilities CLI tool + +This tool was built for generating capability Go types from the JSON schema. + +## Conventions + +- Capability JSON schemas must be named using this pattern `[capability_name]_[capability_type].json`. +- Generated types are placed next to the JSON schema and named using this pattern `[capability_name]_[capability_type]_generated.go`. + +## Running + +_All commands run from the root of the repo._ + +Help: + +```bash +go run ./pkg/capabilities/cli/... +``` + +Generate types: + +```bash +go run ./pkg/capabilities/cli/... generate-types +``` diff --git a/pkg/capabilities/cli/cmd/config_info.go b/pkg/capabilities/cli/cmd/config_info.go new file mode 100644 index 000000000..8d519a81e --- /dev/null +++ b/pkg/capabilities/cli/cmd/config_info.go @@ -0,0 +1,8 @@ +package cmd + +import "github.com/atombender/go-jsonschema/pkg/generator" + +type ConfigInfo struct { + generator.Config + SchemaToTypeInfo map[string]TypeInfo +} diff --git a/pkg/capabilities/cli/cmd/field.go b/pkg/capabilities/cli/cmd/field.go new file mode 100644 index 000000000..ca9297ec8 --- /dev/null +++ b/pkg/capabilities/cli/cmd/field.go @@ -0,0 +1,8 @@ +package cmd + +type Field struct { + Type string + NumSlice int + IsPrimitive bool + ConfigName string +} diff --git a/pkg/capabilities/cli/cmd/generate_types.go b/pkg/capabilities/cli/cmd/generate_types.go new file mode 100644 index 000000000..2bcd61ee4 --- /dev/null +++ b/pkg/capabilities/cli/cmd/generate_types.go @@ -0,0 +1,219 @@ +package cmd + +import ( + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "strings" + + "github.com/atombender/go-jsonschema/pkg/generator" + "github.com/atombender/go-jsonschema/pkg/schemas" + "github.com/spf13/cobra" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" +) + +var Dir string + +// CapabilitySchemaFilePattern is used to extract the package name from the file path. +// This is used as the package name for the generated Go types. +var CapabilitySchemaFilePattern = regexp.MustCompile(`([^/]+)_(action|trigger|consensus|target)-schema\.json$`) + +// reg := regexp.MustCompile(`([^/]+)_(trigger|action)\.json$`) + +func init() { + generateTypesCmd.Flags().StringVar(&Dir, "dir", ".", fmt.Sprintf("Directory to search for %s files, if a file is provided, the directory it is in will be used", CapabilitySchemaFilePattern.String())) + if err := generateTypesCmd.MarkFlagDirname("dir"); err != nil { + fmt.Println(err) + os.Exit(1) + } + + rootCmd.AddCommand(generateTypesCmd) +} + +// Finds all files that match CapabilitySchemaFilePattern in the provided directory and generates Go +// types for each. +var generateTypesCmd = &cobra.Command{ + Use: "generate-types", + Short: "Generate Go types from JSON schema capability definitions", + RunE: func(cmd *cobra.Command, args []string) error { + dir := cmd.Flag("dir").Value.String() + // To allow go generate to work with $GO_FILE + stat, err := os.Stat(dir) + if err != nil { + return err + } + if !stat.IsDir() { + dir = path.Dir(dir) + } + + return GenerateTypes(dir, []WorkflowHelperGenerator{ + &TemplateWorkflowGeneratorHelper{ + Templates: map[string]string{"{{.BaseName|ToSnake}}_builders_generated.go": goWorkflowTemplate}, + }, + }) + }, +} + +func GenerateTypes(dir string, helpers []WorkflowHelperGenerator) error { + schemaPaths, err := schemaFilesFromDir(dir) + if err != nil { + return err + } + + cfgInfo, err := ConfigFromSchemas(schemaPaths) + if err != nil { + return err + } + + for _, schemaPath := range schemaPaths { + if err = generateFromSchema(schemaPath, cfgInfo, helpers); err != nil { + return err + } + } + return nil +} + +func generateFromSchema(schemaPath string, cfgInfo ConfigInfo, helpers []WorkflowHelperGenerator) error { + allFiles := map[string]string{} + file, content, err := TypesFromJSONSchema(schemaPath, cfgInfo) + if err != nil { + return err + } + + capabilityType := cfgInfo.SchemaToTypeInfo[schemaPath].CapabilityType + if err = capabilityType.IsValid(); err != nil { + return fmt.Errorf("invalid capability type %v", capabilityType) + } + + allFiles[file] = content + typeInfo := cfgInfo.SchemaToTypeInfo[schemaPath] + structs, err := generatedInfoFromSrc(content, getCapID(typeInfo), typeInfo) + if err != nil { + return err + } + + if err = generateHelpers(helpers, structs, allFiles); err != nil { + return err + } + + if err = printFiles(path.Dir(schemaPath), allFiles); err != nil { + return err + } + + fmt.Println("Generated types for", schemaPath) + return nil +} + +func getCapID(typeInfo TypeInfo) *string { + id := typeInfo.SchemaID + idParts := strings.Split(id, "/") + id = idParts[len(idParts)-1] + var capID *string + if strings.Contains(id, "@") { + capID = &id + } + return capID +} + +func schemaFilesFromDir(dir string) ([]string, error) { + var schemaPaths []string + + if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + // Ignore directories and files that don't match the CapabilitySchemaFileExtension + if info.IsDir() || !CapabilitySchemaFilePattern.MatchString(path) { + return nil + } + + schemaPaths = append(schemaPaths, path) + return nil + }); err != nil { + return nil, fmt.Errorf("error walking the directory %v: %v", dir, err) + } + return schemaPaths, nil +} + +func generateHelpers(helpers []WorkflowHelperGenerator, structs GeneratedInfo, allFiles map[string]string) error { + for _, helper := range helpers { + files, err := helper.Generate(structs) + if err != nil { + return err + } + + for f, c := range files { + if _, ok := allFiles[f]; ok { + return fmt.Errorf("file %v is being created by more than one generator", f) + } + allFiles[f] = c + } + } + return nil +} + +func ConfigFromSchemas(schemaFilePaths []string) (ConfigInfo, error) { + configInfo := ConfigInfo{ + Config: generator.Config{ + Tags: []string{"json", "yaml", "mapstructure"}, + Warner: func(message string) { fmt.Printf("Warning: %s\n", message) }, + }, + SchemaToTypeInfo: map[string]TypeInfo{}, + } + + for _, schemaFilePath := range schemaFilePaths { + jsonSchema, err := schemas.FromJSONFile(schemaFilePath) + if err != nil { + return configInfo, err + } + + capabilityInfo := CapabilitySchemaFilePattern.FindStringSubmatch(schemaFilePath) + if len(capabilityInfo) != 3 { + return configInfo, fmt.Errorf("invalid schema file path %v", schemaFilePath) + } + + capabilityTypeRaw := capabilityInfo[2] + outputName := strings.Replace(schemaFilePath, capabilityTypeRaw+"-schema.json", capabilityTypeRaw+"_generated.go", 1) + rootType := capitalize(capabilityInfo[2]) + configInfo.SchemaToTypeInfo[schemaFilePath] = TypeInfo{ + CapabilityType: capabilities.CapabilityType(capabilityTypeRaw), + RootType: rootType, + SchemaID: jsonSchema.ID, + } + + configInfo.Config.SchemaMappings = append(configInfo.Config.SchemaMappings, generator.SchemaMapping{ + SchemaID: jsonSchema.ID, + PackageName: path.Dir(jsonSchema.ID[8:]), + RootType: rootType, + OutputName: outputName, + }) + } + return configInfo, nil +} + +// TypesFromJSONSchema generates Go types from a JSON schema file. +func TypesFromJSONSchema(schemaFilePath string, cfgInfo ConfigInfo) (outputFilePath, outputContents string, err error) { + typeInfo := cfgInfo.SchemaToTypeInfo[schemaFilePath] + capabilityType := typeInfo.CapabilityType + outputName := strings.Replace(schemaFilePath, string(capabilityType)+"-schema.json", string(capabilityType)+"_generated.go", 1) + + gen, err := generator.New(cfgInfo.Config) + if err != nil { + return "", "", err + } + + if err = gen.DoFile(schemaFilePath); err != nil { + return "", "", err + } + + generatedContents := gen.Sources() + content := generatedContents[outputName] + + content = []byte(strings.Replace(string(content), "// Code generated by github.com/atombender/go-jsonschema", "// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli", 1)) + + return outputName, string(content), nil +} diff --git a/pkg/capabilities/cli/cmd/generated_info.go b/pkg/capabilities/cli/cmd/generated_info.go new file mode 100644 index 000000000..13590ebdb --- /dev/null +++ b/pkg/capabilities/cli/cmd/generated_info.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "go/ast" + "go/parser" + "go/token" + "reflect" + "strings" + "unicode" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" +) + +type GeneratedInfo struct { + Package string + Config Struct + Input *Struct + Types map[string]Struct + CapabilityType capabilities.CapabilityType + BaseName string + RootOutput string + RootNumSlice int + ExtraImports []string + ID *string +} + +func (g GeneratedInfo) RootType() Struct { + if r, ok := g.Types[g.RootOutput]; ok { + return r + } + return Struct{ + Name: g.RootOutput, + Ref: &g.RootOutput, + } +} + +func generatedInfoFromSrc(src string, capID *string, typeInfo TypeInfo) (GeneratedInfo, error) { + fset := token.NewFileSet() + + // Parse the source code string + node, err := parser.ParseFile(fset, "", src, parser.AllErrors) + if err != nil { + return GeneratedInfo{}, err + } + pkg := node.Name.Name + + generatedStructs := map[string]Struct{} + var extraImports []string + ast.Inspect(node, func(n ast.Node) bool { + return inspectNode(n, fset, src, generatedStructs, &extraImports) + }) + + root := generatedStructs[typeInfo.RootType] + input, config := extractInputAndConfig(generatedStructs, typeInfo, root) + + output := root.Outputs["Outputs"] + + return GeneratedInfo{ + Package: pkg, + Config: config, + Types: generatedStructs, + RootOutput: output.Type, + RootNumSlice: output.NumSlice, + BaseName: typeInfo.RootType, + CapabilityType: typeInfo.CapabilityType, + Input: input, + ExtraImports: extraImports, + ID: capID, + }, nil +} + +func extractInputAndConfig(generatedStructs map[string]Struct, typeInfo TypeInfo, root Struct) (*Struct, Struct) { + delete(generatedStructs, typeInfo.RootType) + inputField, ok := root.Outputs["Inputs"] + var input *Struct + if ok { + inputType := inputField.Type + inputS, ok := generatedStructs[inputType] + if ok { + input = &inputS + delete(generatedStructs, inputType) + } else { + input = &Struct{ + Name: lastAfterDot(inputType), + Ref: &inputType, + } + } + } + + configType := root.Outputs["Config"].Type + config, ok := generatedStructs[configType] + if !ok { + config = Struct{ + Name: lastAfterDot(configType), + Ref: &configType, + } + } + for k := range generatedStructs { + if strings.HasPrefix(k, configType) || (input != nil && strings.HasPrefix(k, input.Name)) { + delete(generatedStructs, k) + } + } + return input, config +} + +func inspectNode(n ast.Node, fset *token.FileSet, src string, rawInfo map[string]Struct, extraImports *[]string) bool { + if ts, ok := n.(*ast.TypeSpec); ok { + s := Struct{ + Name: strings.TrimSpace(ts.Name.Name), + Outputs: map[string]Field{}, + } + + if structType, ok := ts.Type.(*ast.StructType); ok { + for _, field := range structType.Fields.List { + start := fset.Position(field.Type.Pos()).Offset + end := fset.Position(field.Type.End()).Offset + typeStr := src[start:end] + if typeStr == "interface{}" { + typeStr = "any" + } + f := Field{} + + if field.Tag != nil { + tag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]) + jsonTag := tag.Get("json") + if jsonTag != "" { + f.ConfigName = jsonTag + } + } + + f.Type = typeStr + if f.ConfigName == "" { + f.ConfigName = field.Names[0].Name + } + + for strings.HasPrefix(f.Type, "[]") { + f.NumSlice++ + f.Type = f.Type[2:] + } + + f.Type = strings.TrimPrefix(f.Type, "*") + t := f.Type + for t[0] == '*' { + t = t[1:] + } + + f.IsPrimitive = unicode.IsLower(rune(t[0])) + s.Outputs[field.Names[0].Name] = f + } + } + + // artifact used for deserializing + if s.Name != "Plain" { + rawInfo[ts.Name.Name] = s + } + } else if imp, ok := n.(*ast.ImportSpec); ok { + switch imp.Path.Value { + case `"reflect"`, `"fmt"`, `"encoding/json"`: + default: + importStr := imp.Path.Value + if imp.Name != nil { + importStr = imp.Name.Name + " " + importStr + } + *extraImports = append(*extraImports, importStr) + } + } + return true +} + +func lastAfterDot(s string) string { + parts := strings.Split(s, ".") + return parts[len(parts)-1] +} diff --git a/pkg/capabilities/cli/cmd/generator_test.go b/pkg/capabilities/cli/cmd/generator_test.go new file mode 100644 index 000000000..62aa4d1c3 --- /dev/null +++ b/pkg/capabilities/cli/cmd/generator_test.go @@ -0,0 +1,229 @@ +package cmd_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + +//go:generate go run github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli generate-types --dir $GOFILE + +// Notes: +// +// This doesn't get "code coverage" because the generate command is executed before the test +// Since the builder isn't implemented yet, the types added aren't tested yet. +// They will be tested here and in the tests for the builder as well. +func TestTypeGeneration(t *testing.T) { + + t.Run("Basic trigger", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of trigger + var trigger basictrigger.TriggerOutputsCap + trigger = basictrigger.TriggerConfig{ + Name: "anything", + Number: 123, + }.New(factory) + + // verify that the underlying interface is right + var _ workflows.CapDefinition[basictrigger.TriggerOutputs] = trigger + + // verify the type is correct + var expectedOutput workflows.CapDefinition[string] + expectedOutput = trigger.CoolOutput() + _ = expectedOutput + }) + }) + + t.Run("Basic action", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of action + var action basicaction.ActionOutputsCap + action = basicaction.ActionConfig{ + Name: "anything", + Number: 123, + }.New(factory, "reference", basicaction.ActionInput{ + InputThing: workflows.CapDefinition[bool](nil), + }) + + // verify that the underlying interface is right + var _ workflows.CapDefinition[basicaction.ActionOutputs] = action + + // verify the type is correct + var expectedOutput workflows.CapDefinition[string] + expectedOutput = action.AdaptedThing() + _ = expectedOutput + }) + }) + + t.Run("Basic consensus", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of trigger + var consensus basicconsensus.ConsensusOutputsCap + consensus = basicconsensus.ConsensusConfig{ + Name: "anything", + Number: 123, + }.New(factory, "reference", basicconsensus.ConsensusInput{ + InputThing: workflows.CapDefinition[bool](nil), + }) + + // verify that the underlying interface is right + var _ workflows.CapDefinition[basicconsensus.ConsensusOutputs] = consensus + + // verify the type is correct + var expectedConsensusField workflows.CapDefinition[[]string] + expectedConsensusField = consensus.Consensus() + _ = expectedConsensusField + + var expectedSigsField workflows.CapDefinition[[]string] + expectedSigsField = consensus.Sigs() + _ = expectedSigsField + }) + }) + + t.Run("Basic target", func(t *testing.T) { + onlyVerifySyntax(func() { + config := basictarget.TargetConfig{ + Name: "anything", + Number: 123, + } + + // verify no output type + var verifyCreationType func(w *workflows.WorkflowSpecFactory, input basictarget.TargetInput) + verifyCreationType = config.New + var _ = verifyCreationType + }) + }) + t.Run("References", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of action + var action referenceaction.SomeOutputsCap + action = referenceaction.SomeConfig{ + Name: "anything", + Number: 123, + }.New(factory, "reference", referenceaction.ActionInput{ + InputThing: workflows.CapDefinition[bool](nil), + }) + + // verify that the underlying interface is right + var _ workflows.CapDefinition[referenceaction.SomeOutputs] = action + + // verify the type is correct + var expectedOutput workflows.CapDefinition[string] + expectedOutput = action.AdaptedThing() + _ = expectedOutput + }) + }) + + t.Run("External references", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of action + var trigger referenceaction.SomeOutputsCap + config := externalreferenceaction.SomeConfig{ + Name: "anything", + Number: 123, + } + trigger = config.New(factory, "reference", referenceaction.ActionInput{ + InputThing: workflows.CapDefinition[bool](nil), + }) + _ = trigger + + // verify that the type can be cast + cast := referenceaction.SomeConfig(config) + _ = cast + }) + }) + + t.Run("Nested types work", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of action + var action nestedaction.ActionOutputsCap + action = nestedaction.ActionConfig{ + Details: nestedaction.ActionConfigDetails{ + Name: "anything", + Number: 123, + }, + }.New(factory, "reference", nestedaction.ActionInput{ + Metadata: workflows.CapDefinition[nestedaction.ActionInputsMetadata](nil), + }) + + // verify that the underlying interface is right + var _ workflows.CapDefinition[nestedaction.ActionOutputs] = action + + // verify the types are correct + var expectedOutput nestedaction.ActionOutputsResultsCap + var expectedOutputRaw workflows.CapDefinition[nestedaction.ActionOutputsResults] + expectedOutput = action.Results() + expectedOutputRaw = expectedOutput + _ = expectedOutputRaw + + var expectedUnderlyingFieldType workflows.CapDefinition[string] + expectedUnderlyingFieldType = expectedOutput.AdaptedThing() + _ = expectedUnderlyingFieldType + }) + }) + + t.Run("Array types work", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + + // assure the right type of action + var action workflows.CapDefinition[[]arrayaction.ActionOutputsElem] + action = arrayaction.ActionConfig{ + Details: arrayaction.ActionConfigDetails{ + Name: "name", + Number: 123, + }, + }.New(factory, "reference", arrayaction.ActionInput{ + Metadata: workflows.CapDefinition[arrayaction.ActionInputsMetadata](nil), + }) + _ = action + }) + }) + + t.Run("Creating a type from fields works", func(t *testing.T) { + onlyVerifySyntax(func() { + factory := &workflows.WorkflowSpecFactory{} + var action referenceaction.SomeOutputsCap + action = referenceaction.SomeConfig{ + Name: "anything", + Number: 123, + }.New(factory, "reference", referenceaction.ActionInput{ + InputThing: workflows.CapDefinition[bool](nil), + }) + + // verify the type is correct + var adapted basicaction.ActionOutputsCap + adapted = basicaction.NewActionOutputsFromFields(action.AdaptedThing()) + _ = adapted + }) + }) +} + +// onlyVerifySyntax allows testing of the syntax while the builder still isn't implemented. +// The fact that the code compiles, verifies that the generated code works for typing. +func onlyVerifySyntax(run func()) { + defer func() { + recover() + }() + run() +} diff --git a/pkg/capabilities/cli/cmd/root_cmd.go b/pkg/capabilities/cli/cmd/root_cmd.go new file mode 100644 index 000000000..34f1dbc9f --- /dev/null +++ b/pkg/capabilities/cli/cmd/root_cmd.go @@ -0,0 +1,20 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "capability", + Short: "Tooling related to Baku capabilities", +} + +func RootCmd() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/pkg/capabilities/cli/cmd/struct.go b/pkg/capabilities/cli/cmd/struct.go new file mode 100644 index 000000000..4c0efce16 --- /dev/null +++ b/pkg/capabilities/cli/cmd/struct.go @@ -0,0 +1,16 @@ +package cmd + +import "strings" + +type Struct struct { + Name string + Outputs map[string]Field + Ref *string +} + +func (s Struct) RefPkg() string { + if s.Ref == nil { + return "" + } + return strings.Split(*s.Ref, ".")[0] +} diff --git a/pkg/capabilities/cli/cmd/template_workflow_generator_helper.go b/pkg/capabilities/cli/cmd/template_workflow_generator_helper.go new file mode 100644 index 000000000..cd337612e --- /dev/null +++ b/pkg/capabilities/cli/cmd/template_workflow_generator_helper.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "bytes" + _ "embed" + "strings" + "text/template" + + "github.com/iancoleman/strcase" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" +) + +//go:embed templates/go_workflow_builder.go.tmpl +var goWorkflowTemplate string + +type TemplateWorkflowGeneratorHelper struct { + Templates map[string]string +} + +func (t *TemplateWorkflowGeneratorHelper) Generate(info GeneratedInfo) (map[string]string, error) { + files := map[string]string{} + if t.Templates == nil { + return files, nil + } + + for file, t := range t.Templates { + content, err := genFromTemplate(file, t, info) + if err != nil { + return nil, err + } + + // can use a template, but it's simple for now + fileName, err := genFromTemplate("file name for "+file, file, info) + if err != nil { + return nil, err + } + files[fileName] = content + } + + return files, nil +} + +func genFromTemplate(name, rawTemplate string, info GeneratedInfo) (string, error) { + t, err := template.New(name).Funcs(template.FuncMap{ + "LowerFirst": func(s string) string { + if len(s) == 0 { + return s + } + return strings.ToLower(s[:1]) + s[1:] + }, + "Capitalize": capitalize, + "CapitalizeCap": func(c capabilities.CapabilityType) string { + return capitalize(string(c)) + }, + "ToSnake": strcase.ToSnake, + "Repeat": strings.Repeat, + "InputAfterCapability": func() string { + return info.BaseName + "Input" + }, + "HasOutputs": func(tpe string) bool { + return len(info.Types[tpe].Outputs) > 0 + }, + }).Parse(rawTemplate) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + err = t.Execute(buf, info) + return buf.String(), err +} diff --git a/pkg/capabilities/cli/cmd/templates/go_workflow_builder.go.tmpl b/pkg/capabilities/cli/cmd/templates/go_workflow_builder.go.tmpl new file mode 100644 index 000000000..2d5403cb0 --- /dev/null +++ b/pkg/capabilities/cli/cmd/templates/go_workflow_builder.go.tmpl @@ -0,0 +1,150 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package {{.Package}} + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" + + {{- range .ExtraImports }} + {{.}} + {{- end }} +) + +{{ if .Config.Ref }}type {{.Config.Name}} {{.Config.Ref}} {{ end }} + +func (cfg {{.Config.Name}}) New(w *workflows.WorkflowSpecFactory, {{- if not .ID }}id string,{{- end }} {{- if and (ne .CapabilityType "target") (ne .CapabilityType "trigger")}}ref string,{{- end }}{{- if .Input }} input {{InputAfterCapability}}{{- end }}) {{- if ne .CapabilityType "target"}}{{- if eq .RootNumSlice 0}}{{.RootType.Name}}Cap{{- else }}workflows.CapDefinition[{{Repeat "[]" .RootNumSlice}}{{.RootType.Name}}]{{- end }}{{- end }} { + {{ if eq .CapabilityType "trigger" }} ref := "trigger" {{- end }} + def := workflows.StepDefinition{ + ID: {{- if .ID }} "{{.ID}}" {{- else }} id {{- end }}, + {{- if ne .CapabilityType "target"}}Ref: ref, {{- end }} + Inputs: {{- if .Input }} input.ToSteps() {{- else }} workflows.StepInputs{} {{ end }}, + Config: map[string]any{ +{{- range $fieldName, $type := .Config.Outputs }} + "{{$type.ConfigName}}": cfg.{{$fieldName}}, +{{- end }} + }, + CapabilityType: capabilities.CapabilityType{{.CapabilityType|CapitalizeCap}}, + } + + + step := workflows.Step[{{- if eq .CapabilityType "target"}}struct{}{{- else }}{{Repeat "[]" .RootNumSlice}}{{.RootOutput}} {{- end}}]{Definition: def} + {{- if eq .CapabilityType "target" }} + step.AddTo(w) + {{- else if eq 0 .RootNumSlice }} + return {{.RootType.Name}}CapFromStep(w, step) + {{- else }} + return step.AddTo(w) + {{- end }} +} + +{{ range $key, $value := .Types }} +{{- if .Outputs }} +type {{$key}}Cap interface { + workflows.CapDefinition[{{ $key }}] + {{- range $fieldName, $type := .Outputs }} + {{- if $type.IsPrimitive }} + {{$fieldName}}() workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] + {{- else }} + {{$fieldName}}() {{Repeat "[]" $type.NumSlice}}{{ $type.Type }}Cap + {{- end }} + {{- end }} + private() +} + +{{ if ne $.CapabilityType "target" }} +// {{$key}}CapFromStep should only be called from generated code to assure type safety +func {{$key}}CapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[{{$key}}]) {{$key}}Cap { + raw := step.AddTo(w) + return &{{$key|LowerFirst}}{CapDefinition: raw} +} +{{ end }} + +type {{$key|LowerFirst}} struct { + workflows.CapDefinition[{{ $key }}] +} + +func (*{{$key|LowerFirst}}) private() {} + + {{- range $fieldName, $type := .Outputs }} + {{- if or $type.IsPrimitive }} +func (c *{{$key|LowerFirst}}) {{$fieldName}}() workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] { + return workflows.AccessField[{{$value.Name}}, {{Repeat "[]" $type.NumSlice}}{{$type.Type}}](c.CapDefinition, "{{$fieldName}}") +} + {{- else }} +func (c *{{$key|LowerFirst}}) {{$fieldName}}() {{ $type.Type }}Cap { + {{- if $type.Type|HasOutputs }} + return &{{ $type.Type | LowerFirst }}{ CapDefinition: workflows.AccessField[{{$value.Name}}, {{$type.Type}}](c.CapDefinition, "{{$fieldName}}")} + {{- else }} + return {{ $type.Type }}Cap(workflows.AccessField[{{$value.Name}}, {{$type.Type}}](c.CapDefinition, "{{$fieldName}}")) + {{- end }} +} + {{- end }} + {{- end }} + +func New{{$key}}FromFields({{- range $fieldName, $type := .Outputs }} + {{- if $type.IsPrimitive }} + {{$fieldName|LowerFirst}} workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}], + {{- else }} + {{$fieldName|LowerFirst}} {{Repeat "[]" $type.NumSlice}}{{ $type.Type }}Cap, + {{- end }} {{- end }}) {{$key}}Cap { + return &simple{{$key}}{ + CapDefinition: workflows.ComponentCapDefinition[{{$value.Name}}]{ {{- range $fieldName, $type := .Outputs }} + "{{$fieldName|LowerFirst}}": {{$fieldName|LowerFirst}}.Ref(), + {{- end }} + }, + {{- range $fieldName, $type := .Outputs }} + {{$fieldName|LowerFirst}}: {{$fieldName|LowerFirst}}, + {{- end }} + } +} + +type simple{{$key}} struct { + workflows.CapDefinition[{{ $key }}] + {{- range $fieldName, $type := .Outputs }} + {{- if $type.IsPrimitive }} + {{$fieldName|LowerFirst}} workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] + {{- else }} + {{$fieldName|LowerFirst}} {{Repeat "[]" $type.NumSlice}}{{ $type.Type }}Cap + {{- end }} + {{- end }} +} + + {{- range $fieldName, $type := .Outputs }} + {{- if $type.IsPrimitive }} +func (c *simple{{$key}}) {{$fieldName}}() workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] { + {{- else }} +func (c *simple{{$key}}) {{$fieldName}}() {{ $type.Type }}Cap { + {{- end }} + return c.{{$fieldName|LowerFirst}} +} + {{- end }} + +func (c *simple{{$key}}) private() {} +{{- else }} +type {{$key}}Cap workflows.CapDefinition[{{ $key }}] +{{- end }} + +{{ end }} + +{{- if .Input }} + {{- if .Input.Ref }} +type {{InputAfterCapability}} = {{.Input.RefPkg}}.{{InputAfterCapability}} + {{- else }} +type {{InputAfterCapability}} struct { +{{- range $fieldName, $type := .Input.Outputs }} + {{$fieldName}} workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] +{{- end }} +} + +func (input {{InputAfterCapability}}) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + {{- range $fieldName, $type := .Input.Outputs }} + "{{$type.ConfigName}}": input.{{$fieldName}}.Ref(), + {{- end }} + }, + } +} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/action_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/action_builders_generated.go new file mode 100644 index 000000000..0cfd8f940 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/action_builders_generated.go @@ -0,0 +1,127 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package arrayaction + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg ActionConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ActionInput)workflows.CapDefinition[[]ActionOutputsElem] { + + def := workflows.StepDefinition{ + ID: "array-test-action@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "details": cfg.Details, + }, + CapabilityType: capabilities.CapabilityTypeAction, + } + + + step := workflows.Step[[]ActionOutputsElem]{Definition: def} + return step.AddTo(w) +} + + +type ActionOutputsElemCap interface { + workflows.CapDefinition[ActionOutputsElem] + Results() ActionOutputsElemResultsCap + private() +} + + +// ActionOutputsElemCapFromStep should only be called from generated code to assure type safety +func ActionOutputsElemCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ActionOutputsElem]) ActionOutputsElemCap { + raw := step.AddTo(w) + return &actionOutputsElem{CapDefinition: raw} +} + + +type actionOutputsElem struct { + workflows.CapDefinition[ActionOutputsElem] +} + +func (*actionOutputsElem) private() {} +func (c *actionOutputsElem) Results() ActionOutputsElemResultsCap { + return &actionOutputsElemResults{ CapDefinition: workflows.AccessField[ActionOutputsElem, ActionOutputsElemResults](c.CapDefinition, "Results")} +} + +func NewActionOutputsElemFromFields( + results ActionOutputsElemResultsCap,) ActionOutputsElemCap { + return &simpleActionOutputsElem{ + CapDefinition: workflows.ComponentCapDefinition[ActionOutputsElem]{ + "results": results.Ref(), + }, + results: results, + } +} + +type simpleActionOutputsElem struct { + workflows.CapDefinition[ActionOutputsElem] + results ActionOutputsElemResultsCap +} +func (c *simpleActionOutputsElem) Results() ActionOutputsElemResultsCap { + return c.results +} + +func (c *simpleActionOutputsElem) private() {} + + +type ActionOutputsElemResultsCap interface { + workflows.CapDefinition[ActionOutputsElemResults] + AdaptedThing() workflows.CapDefinition[string] + private() +} + + +// ActionOutputsElemResultsCapFromStep should only be called from generated code to assure type safety +func ActionOutputsElemResultsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ActionOutputsElemResults]) ActionOutputsElemResultsCap { + raw := step.AddTo(w) + return &actionOutputsElemResults{CapDefinition: raw} +} + + +type actionOutputsElemResults struct { + workflows.CapDefinition[ActionOutputsElemResults] +} + +func (*actionOutputsElemResults) private() {} +func (c *actionOutputsElemResults) AdaptedThing() workflows.CapDefinition[string] { + return workflows.AccessField[ActionOutputsElemResults, string](c.CapDefinition, "AdaptedThing") +} + +func NewActionOutputsElemResultsFromFields( + adaptedThing workflows.CapDefinition[string],) ActionOutputsElemResultsCap { + return &simpleActionOutputsElemResults{ + CapDefinition: workflows.ComponentCapDefinition[ActionOutputsElemResults]{ + "adaptedThing": adaptedThing.Ref(), + }, + adaptedThing: adaptedThing, + } +} + +type simpleActionOutputsElemResults struct { + workflows.CapDefinition[ActionOutputsElemResults] + adaptedThing workflows.CapDefinition[string] +} +func (c *simpleActionOutputsElemResults) AdaptedThing() workflows.CapDefinition[string] { + return c.adaptedThing +} + +func (c *simpleActionOutputsElemResults) private() {} + + +type ActionInput struct { + Metadata workflows.CapDefinition[ActionInputsMetadata] +} + +func (input ActionInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "metadata": input.Metadata.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action-schema.json new file mode 100644 index 000000000..31e9df3dd --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action-schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array-test-action@1.0.0", + "description": "Array Test Action", + "properties": { + "config": { + "type": "object", + "properties": { + "details": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "required": ["name", "number"], + "additionalProperties": false + } + }, + "required": ["details"], + "additionalProperties": false + }, + "inputs": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "properties": { + "input_thing": { + "type": "boolean" + } + }, + "required": ["input_thing"], + "additionalProperties": false + } + }, + "required": ["metadata"], + "additionalProperties": false + }, + "outputs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "results": { + "type": "object", + "properties": { + "adapted_thing": { + "type": "string" + } + }, + "required": ["adapted_thing"], + "additionalProperties": false + } + } + } + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action_generated.go new file mode 100644 index 000000000..e201e366b --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/arrayaction/array_action_generated.go @@ -0,0 +1,162 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package arrayaction + +import "encoding/json" +import "fmt" + +// Array Test Action +type Action struct { + // Config corresponds to the JSON schema field "config". + Config ActionConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *ActionInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs []ActionOutputsElem `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type ActionConfig struct { + // Details corresponds to the JSON schema field "details". + Details ActionConfigDetails `json:"details" yaml:"details" mapstructure:"details"` +} + +type ActionConfigDetails struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionConfigDetails) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in ActionConfigDetails: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in ActionConfigDetails: required") + } + type Plain ActionConfigDetails + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionConfigDetails(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["details"]; raw != nil && !ok { + return fmt.Errorf("field details in ActionConfig: required") + } + type Plain ActionConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionConfig(plain) + return nil +} + +type ActionInputs struct { + // Metadata corresponds to the JSON schema field "metadata". + Metadata ActionInputsMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` +} + +type ActionInputsMetadata struct { + // InputThing corresponds to the JSON schema field "input_thing". + InputThing bool `json:"input_thing" yaml:"input_thing" mapstructure:"input_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionInputsMetadata) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["input_thing"]; raw != nil && !ok { + return fmt.Errorf("field input_thing in ActionInputsMetadata: required") + } + type Plain ActionInputsMetadata + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionInputsMetadata(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["metadata"]; raw != nil && !ok { + return fmt.Errorf("field metadata in ActionInputs: required") + } + type Plain ActionInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionInputs(plain) + return nil +} + +type ActionOutputsElem struct { + // Results corresponds to the JSON schema field "results". + Results *ActionOutputsElemResults `json:"results,omitempty" yaml:"results,omitempty" mapstructure:"results,omitempty"` +} + +type ActionOutputsElemResults struct { + // AdaptedThing corresponds to the JSON schema field "adapted_thing". + AdaptedThing string `json:"adapted_thing" yaml:"adapted_thing" mapstructure:"adapted_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionOutputsElemResults) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["adapted_thing"]; raw != nil && !ok { + return fmt.Errorf("field adapted_thing in ActionOutputsElemResults: required") + } + type Plain ActionOutputsElemResults + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionOutputsElemResults(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Action) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Action: required") + } + type Plain Action + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Action(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/action_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/action_builders_generated.go new file mode 100644 index 000000000..78a80a9e7 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/action_builders_generated.go @@ -0,0 +1,84 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basicaction + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg ActionConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ActionInput)ActionOutputsCap { + + def := workflows.StepDefinition{ + ID: "basic-test-action@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "name": cfg.Name, + "number": cfg.Number, + }, + CapabilityType: capabilities.CapabilityTypeAction, + } + + + step := workflows.Step[ActionOutputs]{Definition: def} + return ActionOutputsCapFromStep(w, step) +} + + +type ActionOutputsCap interface { + workflows.CapDefinition[ActionOutputs] + AdaptedThing() workflows.CapDefinition[string] + private() +} + + +// ActionOutputsCapFromStep should only be called from generated code to assure type safety +func ActionOutputsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ActionOutputs]) ActionOutputsCap { + raw := step.AddTo(w) + return &actionOutputs{CapDefinition: raw} +} + + +type actionOutputs struct { + workflows.CapDefinition[ActionOutputs] +} + +func (*actionOutputs) private() {} +func (c *actionOutputs) AdaptedThing() workflows.CapDefinition[string] { + return workflows.AccessField[ActionOutputs, string](c.CapDefinition, "AdaptedThing") +} + +func NewActionOutputsFromFields( + adaptedThing workflows.CapDefinition[string],) ActionOutputsCap { + return &simpleActionOutputs{ + CapDefinition: workflows.ComponentCapDefinition[ActionOutputs]{ + "adaptedThing": adaptedThing.Ref(), + }, + adaptedThing: adaptedThing, + } +} + +type simpleActionOutputs struct { + workflows.CapDefinition[ActionOutputs] + adaptedThing workflows.CapDefinition[string] +} +func (c *simpleActionOutputs) AdaptedThing() workflows.CapDefinition[string] { + return c.adaptedThing +} + +func (c *simpleActionOutputs) private() {} + + +type ActionInput struct { + InputThing workflows.CapDefinition[bool] +} + +func (input ActionInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "input_thing": input.InputThing.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action-schema.json new file mode 100644 index 000000000..59b81901e --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action-schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic-test-action@1.0.0", + "description": "Basic Test Action", + "properties": { + "config": { + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + }, + "inputs" : { + "properties": { + "input_thing": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["input_thing"] + }, + "outputs": { + "properties": { + "adapted_thing": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action_generated.go new file mode 100644 index 000000000..282d2259b --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicaction/basic_action_generated.go @@ -0,0 +1,93 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basicaction + +import "encoding/json" +import "fmt" + +// Basic Test Action +type Action struct { + // Config corresponds to the JSON schema field "config". + Config ActionConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *ActionInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs *ActionOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type ActionConfig struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in ActionConfig: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in ActionConfig: required") + } + type Plain ActionConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionConfig(plain) + return nil +} + +type ActionInputs struct { + // InputThing corresponds to the JSON schema field "input_thing". + InputThing bool `json:"input_thing" yaml:"input_thing" mapstructure:"input_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["input_thing"]; raw != nil && !ok { + return fmt.Errorf("field input_thing in ActionInputs: required") + } + type Plain ActionInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionInputs(plain) + return nil +} + +type ActionOutputs struct { + // AdaptedThing corresponds to the JSON schema field "adapted_thing". + AdaptedThing *string `json:"adapted_thing,omitempty" yaml:"adapted_thing,omitempty" mapstructure:"adapted_thing,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Action) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Action: required") + } + type Plain Action + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Action(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus-schema.json new file mode 100644 index 000000000..c623f317f --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus-schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic-test-consensus@1.0.0", + "description": "Basic Test Consensus", + "properties": { + "config": { + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + }, + "inputs": { + "properties": { + "input_thing": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["input_thing"] + }, + "outputs": { + "properties": { + "consensus": { + "type": "array", + "items": { + "type": "string" + } + }, + "sigs": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "NOTE, OCR3 is the consensus we support that has output as signed report, this is simply testing that generation is right", + "additionalProperties": false, + "type": "object", + "required": ["consensus", "sigs"] + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus_generated.go new file mode 100644 index 000000000..1d3d5d264 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/basic_consensus_generated.go @@ -0,0 +1,120 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basicconsensus + +import "encoding/json" +import "fmt" + +// Basic Test Consensus +type Consensus struct { + // Config corresponds to the JSON schema field "config". + Config ConsensusConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *ConsensusInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // NOTE, OCR3 is the consensus we support that has output as signed report, this + // is simply testing that generation is right + Outputs *ConsensusOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type ConsensusConfig struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in ConsensusConfig: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in ConsensusConfig: required") + } + type Plain ConsensusConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusConfig(plain) + return nil +} + +type ConsensusInputs struct { + // InputThing corresponds to the JSON schema field "input_thing". + InputThing bool `json:"input_thing" yaml:"input_thing" mapstructure:"input_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["input_thing"]; raw != nil && !ok { + return fmt.Errorf("field input_thing in ConsensusInputs: required") + } + type Plain ConsensusInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusInputs(plain) + return nil +} + +// NOTE, OCR3 is the consensus we support that has output as signed report, this is +// simply testing that generation is right +type ConsensusOutputs struct { + // Consensus corresponds to the JSON schema field "consensus". + Consensus []string `json:"consensus" yaml:"consensus" mapstructure:"consensus"` + + // Sigs corresponds to the JSON schema field "sigs". + Sigs []string `json:"sigs" yaml:"sigs" mapstructure:"sigs"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusOutputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["consensus"]; raw != nil && !ok { + return fmt.Errorf("field consensus in ConsensusOutputs: required") + } + if _, ok := raw["sigs"]; raw != nil && !ok { + return fmt.Errorf("field sigs in ConsensusOutputs: required") + } + type Plain ConsensusOutputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusOutputs(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Consensus) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Consensus: required") + } + type Plain Consensus + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Consensus(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/consensus_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/consensus_builders_generated.go new file mode 100644 index 000000000..08eab7d25 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basicconsensus/consensus_builders_generated.go @@ -0,0 +1,95 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basicconsensus + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg ConsensusConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ConsensusInput)ConsensusOutputsCap { + + def := workflows.StepDefinition{ + ID: "basic-test-consensus@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "name": cfg.Name, + "number": cfg.Number, + }, + CapabilityType: capabilities.CapabilityTypeConsensus, + } + + + step := workflows.Step[ConsensusOutputs]{Definition: def} + return ConsensusOutputsCapFromStep(w, step) +} + + +type ConsensusOutputsCap interface { + workflows.CapDefinition[ConsensusOutputs] + Consensus() workflows.CapDefinition[[]string] + Sigs() workflows.CapDefinition[[]string] + private() +} + + +// ConsensusOutputsCapFromStep should only be called from generated code to assure type safety +func ConsensusOutputsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ConsensusOutputs]) ConsensusOutputsCap { + raw := step.AddTo(w) + return &consensusOutputs{CapDefinition: raw} +} + + +type consensusOutputs struct { + workflows.CapDefinition[ConsensusOutputs] +} + +func (*consensusOutputs) private() {} +func (c *consensusOutputs) Consensus() workflows.CapDefinition[[]string] { + return workflows.AccessField[ConsensusOutputs, []string](c.CapDefinition, "Consensus") +} +func (c *consensusOutputs) Sigs() workflows.CapDefinition[[]string] { + return workflows.AccessField[ConsensusOutputs, []string](c.CapDefinition, "Sigs") +} + +func NewConsensusOutputsFromFields( + consensus workflows.CapDefinition[[]string], + sigs workflows.CapDefinition[[]string],) ConsensusOutputsCap { + return &simpleConsensusOutputs{ + CapDefinition: workflows.ComponentCapDefinition[ConsensusOutputs]{ + "consensus": consensus.Ref(), + "sigs": sigs.Ref(), + }, + consensus: consensus, + sigs: sigs, + } +} + +type simpleConsensusOutputs struct { + workflows.CapDefinition[ConsensusOutputs] + consensus workflows.CapDefinition[[]string] + sigs workflows.CapDefinition[[]string] +} +func (c *simpleConsensusOutputs) Consensus() workflows.CapDefinition[[]string] { + return c.consensus +} +func (c *simpleConsensusOutputs) Sigs() workflows.CapDefinition[[]string] { + return c.sigs +} + +func (c *simpleConsensusOutputs) private() {} + + +type ConsensusInput struct { + InputThing workflows.CapDefinition[bool] +} + +func (input ConsensusInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "input_thing": input.InputThing.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target-schema.json new file mode 100644 index 000000000..0ef24d2f3 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target-schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic-test-target@1.0.0", + "description": "Basic Test Target", + "properties": { + "config": { + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"], + "description" : "NOTE that real targets would likely take consensus as input, this is only for testing" + }, + "inputs": { + "properties": { + "cool_input": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target_generated.go new file mode 100644 index 000000000..d98822e0b --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/basic_target_generated.go @@ -0,0 +1,70 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basictarget + +import "encoding/json" +import "fmt" + +// Basic Test Target +type Target struct { + // NOTE that real targets would likely take consensus as input, this is only for + // testing + Config TargetConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *TargetInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` +} + +// NOTE that real targets would likely take consensus as input, this is only for +// testing +type TargetConfig struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TargetConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in TargetConfig: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in TargetConfig: required") + } + type Plain TargetConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TargetConfig(plain) + return nil +} + +type TargetInputs struct { + // CoolInput corresponds to the JSON schema field "cool_input". + CoolInput *string `json:"cool_input,omitempty" yaml:"cool_input,omitempty" mapstructure:"cool_input,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Target) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Target: required") + } + type Plain Target + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Target(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/target_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/target_builders_generated.go new file mode 100644 index 000000000..01efe113b --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictarget/target_builders_generated.go @@ -0,0 +1,40 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basictarget + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg TargetConfig) New(w *workflows.WorkflowSpecFactory, input TargetInput) { + + def := workflows.StepDefinition{ + ID: "basic-test-target@1.0.0", + Inputs: input.ToSteps(), + Config: map[string]any{ + "name": cfg.Name, + "number": cfg.Number, + }, + CapabilityType: capabilities.CapabilityTypeTarget, + } + + + step := workflows.Step[struct{}]{Definition: def} + step.AddTo(w) +} + + +type TargetInput struct { + CoolInput workflows.CapDefinition[string] +} + +func (input TargetInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "cool_input,omitempty": input.CoolInput.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger-schema.json new file mode 100644 index 000000000..ced2aa708 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic-test-trigger@1.0.0", + "description": "Basic Test Trigger", + "properties": { + "config": { + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + }, + "outputs": { + "properties": { + "cool_output": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["name", "number"] + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger_generated.go new file mode 100644 index 000000000..c11e7d563 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/basic_trigger_generated.go @@ -0,0 +1,67 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basictrigger + +import "encoding/json" +import "fmt" + +// Basic Test Trigger +type Trigger struct { + // Config corresponds to the JSON schema field "config". + Config TriggerConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs *TriggerOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type TriggerConfig struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TriggerConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in TriggerConfig: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in TriggerConfig: required") + } + type Plain TriggerConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TriggerConfig(plain) + return nil +} + +type TriggerOutputs struct { + // CoolOutput corresponds to the JSON schema field "cool_output". + CoolOutput *string `json:"cool_output,omitempty" yaml:"cool_output,omitempty" mapstructure:"cool_output,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Trigger) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Trigger: required") + } + type Plain Trigger + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Trigger(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/trigger_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/trigger_builders_generated.go new file mode 100644 index 000000000..ac33c02b0 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/basictrigger/trigger_builders_generated.go @@ -0,0 +1,72 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package basictrigger + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg TriggerConfig) New(w *workflows.WorkflowSpecFactory,)TriggerOutputsCap { + ref := "trigger" + def := workflows.StepDefinition{ + ID: "basic-test-trigger@1.0.0",Ref: ref, + Inputs: workflows.StepInputs{} , + Config: map[string]any{ + "name": cfg.Name, + "number": cfg.Number, + }, + CapabilityType: capabilities.CapabilityTypeTrigger, + } + + + step := workflows.Step[TriggerOutputs]{Definition: def} + return TriggerOutputsCapFromStep(w, step) +} + + +type TriggerOutputsCap interface { + workflows.CapDefinition[TriggerOutputs] + CoolOutput() workflows.CapDefinition[string] + private() +} + + +// TriggerOutputsCapFromStep should only be called from generated code to assure type safety +func TriggerOutputsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[TriggerOutputs]) TriggerOutputsCap { + raw := step.AddTo(w) + return &triggerOutputs{CapDefinition: raw} +} + + +type triggerOutputs struct { + workflows.CapDefinition[TriggerOutputs] +} + +func (*triggerOutputs) private() {} +func (c *triggerOutputs) CoolOutput() workflows.CapDefinition[string] { + return workflows.AccessField[TriggerOutputs, string](c.CapDefinition, "CoolOutput") +} + +func NewTriggerOutputsFromFields( + coolOutput workflows.CapDefinition[string],) TriggerOutputsCap { + return &simpleTriggerOutputs{ + CapDefinition: workflows.ComponentCapDefinition[TriggerOutputs]{ + "coolOutput": coolOutput.Ref(), + }, + coolOutput: coolOutput, + } +} + +type simpleTriggerOutputs struct { + workflows.CapDefinition[TriggerOutputs] + coolOutput workflows.CapDefinition[string] +} +func (c *simpleTriggerOutputs) CoolOutput() workflows.CapDefinition[string] { + return c.coolOutput +} + +func (c *simpleTriggerOutputs) private() {} + diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/action_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/action_builders_generated.go new file mode 100644 index 000000000..f4d676f04 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/action_builders_generated.go @@ -0,0 +1,29 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package externalreferenceaction + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" + referenceaction "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction" +) + +type SomeConfig referenceaction.SomeConfig + +func (cfg SomeConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ActionInput)referenceaction.SomeOutputsCap { + + def := workflows.StepDefinition{ + ID: "external-reference-test-action@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + }, + CapabilityType: capabilities.CapabilityTypeAction, + } + + + step := workflows.Step[referenceaction.SomeOutputs]{Definition: def} + return referenceaction.SomeOutputsCapFromStep(w, step) +} + + +type ActionInput = referenceaction.ActionInput \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action-schema.json new file mode 100644 index 000000000..b79e1c0cf --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action-schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/external-reference-test-action@1.0.0", + "description": "External Reference Test Action", + "properties": { + "config": { + "$ref": "../referenceaction/reference_action-schema.json#/$defs/SomeConfig" + }, + "inputs": { + "$ref": "../referenceaction/reference_action-schema.json#/$defs/SomeInputs" + }, + "outputs": { + "$ref": "../referenceaction/reference_action-schema.json#/$defs/SomeOutputs" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action_generated.go new file mode 100644 index 000000000..45d882d6a --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/externalreferenceaction/externalreferenceaction_action_generated.go @@ -0,0 +1,37 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package externalreferenceaction + +import "encoding/json" +import "fmt" +import referenceaction "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction" + +// External Reference Test Action +type Action struct { + // Config corresponds to the JSON schema field "config". + Config referenceaction.SomeConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *referenceaction.SomeInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs *referenceaction.SomeOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Action) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Action: required") + } + type Plain Action + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Action(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/action_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/action_builders_generated.go new file mode 100644 index 000000000..7ae140b6e --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/action_builders_generated.go @@ -0,0 +1,127 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package nestedaction + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg ActionConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ActionInput)ActionOutputsCap { + + def := workflows.StepDefinition{ + ID: "nested-test-action@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "details": cfg.Details, + }, + CapabilityType: capabilities.CapabilityTypeAction, + } + + + step := workflows.Step[ActionOutputs]{Definition: def} + return ActionOutputsCapFromStep(w, step) +} + + +type ActionOutputsCap interface { + workflows.CapDefinition[ActionOutputs] + Results() ActionOutputsResultsCap + private() +} + + +// ActionOutputsCapFromStep should only be called from generated code to assure type safety +func ActionOutputsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ActionOutputs]) ActionOutputsCap { + raw := step.AddTo(w) + return &actionOutputs{CapDefinition: raw} +} + + +type actionOutputs struct { + workflows.CapDefinition[ActionOutputs] +} + +func (*actionOutputs) private() {} +func (c *actionOutputs) Results() ActionOutputsResultsCap { + return &actionOutputsResults{ CapDefinition: workflows.AccessField[ActionOutputs, ActionOutputsResults](c.CapDefinition, "Results")} +} + +func NewActionOutputsFromFields( + results ActionOutputsResultsCap,) ActionOutputsCap { + return &simpleActionOutputs{ + CapDefinition: workflows.ComponentCapDefinition[ActionOutputs]{ + "results": results.Ref(), + }, + results: results, + } +} + +type simpleActionOutputs struct { + workflows.CapDefinition[ActionOutputs] + results ActionOutputsResultsCap +} +func (c *simpleActionOutputs) Results() ActionOutputsResultsCap { + return c.results +} + +func (c *simpleActionOutputs) private() {} + + +type ActionOutputsResultsCap interface { + workflows.CapDefinition[ActionOutputsResults] + AdaptedThing() workflows.CapDefinition[string] + private() +} + + +// ActionOutputsResultsCapFromStep should only be called from generated code to assure type safety +func ActionOutputsResultsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[ActionOutputsResults]) ActionOutputsResultsCap { + raw := step.AddTo(w) + return &actionOutputsResults{CapDefinition: raw} +} + + +type actionOutputsResults struct { + workflows.CapDefinition[ActionOutputsResults] +} + +func (*actionOutputsResults) private() {} +func (c *actionOutputsResults) AdaptedThing() workflows.CapDefinition[string] { + return workflows.AccessField[ActionOutputsResults, string](c.CapDefinition, "AdaptedThing") +} + +func NewActionOutputsResultsFromFields( + adaptedThing workflows.CapDefinition[string],) ActionOutputsResultsCap { + return &simpleActionOutputsResults{ + CapDefinition: workflows.ComponentCapDefinition[ActionOutputsResults]{ + "adaptedThing": adaptedThing.Ref(), + }, + adaptedThing: adaptedThing, + } +} + +type simpleActionOutputsResults struct { + workflows.CapDefinition[ActionOutputsResults] + adaptedThing workflows.CapDefinition[string] +} +func (c *simpleActionOutputsResults) AdaptedThing() workflows.CapDefinition[string] { + return c.adaptedThing +} + +func (c *simpleActionOutputsResults) private() {} + + +type ActionInput struct { + Metadata workflows.CapDefinition[ActionInputsMetadata] +} + +func (input ActionInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "metadata": input.Metadata.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action-schema.json new file mode 100644 index 000000000..5b345d5f4 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action-schema.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested-test-action@1.0.0", + "description": "Nested Test Action", + "properties": { + "config": { + "type": "object", + "properties": { + "details": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "required": ["name", "number"], + "additionalProperties": false + } + }, + "required": ["details"], + "additionalProperties": false + }, + "inputs": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "properties": { + "input_thing": { + "type": "boolean" + } + }, + "required": ["input_thing"], + "additionalProperties": false + } + }, + "required": ["metadata"], + "additionalProperties": false + }, + "outputs": { + "type": "object", + "properties": { + "results": { + "type": "object", + "properties": { + "adapted_thing": { + "type": "string" + } + }, + "required": ["adapted_thing"], + "additionalProperties": false + } + }, + "required": ["results"], + "additionalProperties": false + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action_generated.go new file mode 100644 index 000000000..38884b928 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/nestedaction/nested_action_generated.go @@ -0,0 +1,180 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package nestedaction + +import "encoding/json" +import "fmt" + +// Nested Test Action +type Action struct { + // Config corresponds to the JSON schema field "config". + Config ActionConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *ActionInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs *ActionOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type ActionConfig struct { + // Details corresponds to the JSON schema field "details". + Details ActionConfigDetails `json:"details" yaml:"details" mapstructure:"details"` +} + +type ActionConfigDetails struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionConfigDetails) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in ActionConfigDetails: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in ActionConfigDetails: required") + } + type Plain ActionConfigDetails + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionConfigDetails(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["details"]; raw != nil && !ok { + return fmt.Errorf("field details in ActionConfig: required") + } + type Plain ActionConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionConfig(plain) + return nil +} + +type ActionInputs struct { + // Metadata corresponds to the JSON schema field "metadata". + Metadata ActionInputsMetadata `json:"metadata" yaml:"metadata" mapstructure:"metadata"` +} + +type ActionInputsMetadata struct { + // InputThing corresponds to the JSON schema field "input_thing". + InputThing bool `json:"input_thing" yaml:"input_thing" mapstructure:"input_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionInputsMetadata) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["input_thing"]; raw != nil && !ok { + return fmt.Errorf("field input_thing in ActionInputsMetadata: required") + } + type Plain ActionInputsMetadata + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionInputsMetadata(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["metadata"]; raw != nil && !ok { + return fmt.Errorf("field metadata in ActionInputs: required") + } + type Plain ActionInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionInputs(plain) + return nil +} + +type ActionOutputs struct { + // Results corresponds to the JSON schema field "results". + Results ActionOutputsResults `json:"results" yaml:"results" mapstructure:"results"` +} + +type ActionOutputsResults struct { + // AdaptedThing corresponds to the JSON schema field "adapted_thing". + AdaptedThing string `json:"adapted_thing" yaml:"adapted_thing" mapstructure:"adapted_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionOutputsResults) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["adapted_thing"]; raw != nil && !ok { + return fmt.Errorf("field adapted_thing in ActionOutputsResults: required") + } + type Plain ActionOutputsResults + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionOutputsResults(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ActionOutputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["results"]; raw != nil && !ok { + return fmt.Errorf("field results in ActionOutputs: required") + } + type Plain ActionOutputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ActionOutputs(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Action) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Action: required") + } + type Plain Action + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Action(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/action_builders_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/action_builders_generated.go new file mode 100644 index 000000000..e6d6b1bfc --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/action_builders_generated.go @@ -0,0 +1,84 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package referenceaction + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg SomeConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ActionInput)SomeOutputsCap { + + def := workflows.StepDefinition{ + ID: "reference-test-action@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "name": cfg.Name, + "number": cfg.Number, + }, + CapabilityType: capabilities.CapabilityTypeAction, + } + + + step := workflows.Step[SomeOutputs]{Definition: def} + return SomeOutputsCapFromStep(w, step) +} + + +type SomeOutputsCap interface { + workflows.CapDefinition[SomeOutputs] + AdaptedThing() workflows.CapDefinition[string] + private() +} + + +// SomeOutputsCapFromStep should only be called from generated code to assure type safety +func SomeOutputsCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[SomeOutputs]) SomeOutputsCap { + raw := step.AddTo(w) + return &someOutputs{CapDefinition: raw} +} + + +type someOutputs struct { + workflows.CapDefinition[SomeOutputs] +} + +func (*someOutputs) private() {} +func (c *someOutputs) AdaptedThing() workflows.CapDefinition[string] { + return workflows.AccessField[SomeOutputs, string](c.CapDefinition, "AdaptedThing") +} + +func NewSomeOutputsFromFields( + adaptedThing workflows.CapDefinition[string],) SomeOutputsCap { + return &simpleSomeOutputs{ + CapDefinition: workflows.ComponentCapDefinition[SomeOutputs]{ + "adaptedThing": adaptedThing.Ref(), + }, + adaptedThing: adaptedThing, + } +} + +type simpleSomeOutputs struct { + workflows.CapDefinition[SomeOutputs] + adaptedThing workflows.CapDefinition[string] +} +func (c *simpleSomeOutputs) AdaptedThing() workflows.CapDefinition[string] { + return c.adaptedThing +} + +func (c *simpleSomeOutputs) private() {} + + +type ActionInput struct { + InputThing workflows.CapDefinition[bool] +} + +func (input ActionInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "input_thing": input.InputThing.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action-schema.json b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action-schema.json new file mode 100644 index 000000000..b50ea8804 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action-schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference-test-action@1.0.0", + "description": "Basic Test Action", + "$defs": { + "SomeConfig": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer", + "description": "The interval in seconds after which a new trigger event is generated.", + "minimum": 1 + } + }, + "additionalProperties": false, + "required": ["name", "number"] + }, + "SomeInputs": { + "type": "object", + "properties": { + "input_thing": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": ["input_thing"] + }, + "SomeOutputs": { + "type": "object", + "properties": { + "adapted_thing": { + "type": "string" + } + }, + "additionalProperties": false, + "required": ["adapted_thing"] + } + }, + "properties": { + "config": { + "$ref": "#/$defs/SomeConfig" + }, + "inputs": { + "$ref": "#/$defs/SomeInputs" + }, + "outputs": { + "$ref": "#/$defs/SomeOutputs" + } + }, + "additionalProperties": false, + "type": "object", + "required": ["config"] +} diff --git a/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action_generated.go b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action_generated.go new file mode 100644 index 000000000..cb1ff31d7 --- /dev/null +++ b/pkg/capabilities/cli/cmd/testdata/fixtures/capabilities/referenceaction/reference_action_generated.go @@ -0,0 +1,111 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package referenceaction + +import "encoding/json" +import "fmt" + +// Basic Test Action +type Action struct { + // Config corresponds to the JSON schema field "config". + Config SomeConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs *SomeInputs `json:"inputs,omitempty" yaml:"inputs,omitempty" mapstructure:"inputs,omitempty"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs *SomeOutputs `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Action) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Action: required") + } + type Plain Action + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Action(plain) + return nil +} + +type SomeConfig struct { + // Name corresponds to the JSON schema field "name". + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The interval in seconds after which a new trigger event is generated. + Number int `json:"number" yaml:"number" mapstructure:"number"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SomeConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in SomeConfig: required") + } + if _, ok := raw["number"]; raw != nil && !ok { + return fmt.Errorf("field number in SomeConfig: required") + } + type Plain SomeConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SomeConfig(plain) + return nil +} + +type SomeInputs struct { + // InputThing corresponds to the JSON schema field "input_thing". + InputThing bool `json:"input_thing" yaml:"input_thing" mapstructure:"input_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SomeInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["input_thing"]; raw != nil && !ok { + return fmt.Errorf("field input_thing in SomeInputs: required") + } + type Plain SomeInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SomeInputs(plain) + return nil +} + +type SomeOutputs struct { + // AdaptedThing corresponds to the JSON schema field "adapted_thing". + AdaptedThing string `json:"adapted_thing" yaml:"adapted_thing" mapstructure:"adapted_thing"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SomeOutputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["adapted_thing"]; raw != nil && !ok { + return fmt.Errorf("field adapted_thing in SomeOutputs: required") + } + type Plain SomeOutputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SomeOutputs(plain) + return nil +} diff --git a/pkg/capabilities/cli/cmd/type_info.go b/pkg/capabilities/cli/cmd/type_info.go new file mode 100644 index 000000000..00b8bacfb --- /dev/null +++ b/pkg/capabilities/cli/cmd/type_info.go @@ -0,0 +1,9 @@ +package cmd + +import "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + +type TypeInfo struct { + CapabilityType capabilities.CapabilityType + RootType string + SchemaID string +} diff --git a/pkg/capabilities/cli/cmd/utils.go b/pkg/capabilities/cli/cmd/utils.go new file mode 100644 index 000000000..89ddfde82 --- /dev/null +++ b/pkg/capabilities/cli/cmd/utils.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "os" + "path" + "strings" +) + +func printFiles(dir string, files map[string]string) error { + for file, content := range files { + if !strings.HasPrefix(file, dir) { + file = dir + "/" + file + } + + if err := os.MkdirAll(path.Dir(file), 0600); err != nil { + return err + } + + if err := os.WriteFile(file, []byte(content), 0600); err != nil { + return err + } + } + + return nil +} + +func capitalize(s string) string { + return strings.ToUpper(string(s[0])) + s[1:] +} diff --git a/pkg/capabilities/cli/cmd/workflow_helper_generator.go b/pkg/capabilities/cli/cmd/workflow_helper_generator.go new file mode 100644 index 000000000..5e10c3efa --- /dev/null +++ b/pkg/capabilities/cli/cmd/workflow_helper_generator.go @@ -0,0 +1,5 @@ +package cmd + +type WorkflowHelperGenerator interface { + Generate(info GeneratedInfo) (map[string]string, error) +} diff --git a/pkg/capabilities/cli/main.go b/pkg/capabilities/cli/main.go new file mode 100644 index 000000000..bc8cc3868 --- /dev/null +++ b/pkg/capabilities/cli/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli/cmd" +) + +func main() { + cmd.RootCmd() +} diff --git a/pkg/capabilities/consensus/ocr3/consensus_builders_generated.go b/pkg/capabilities/consensus/ocr3/consensus_builders_generated.go new file mode 100644 index 000000000..3a5ca72fb --- /dev/null +++ b/pkg/capabilities/consensus/ocr3/consensus_builders_generated.go @@ -0,0 +1,223 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package ocr3 + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" + streams "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers/streams" +) + + + +func (cfg ConsensusConfig) New(w *workflows.WorkflowSpecFactory,ref string, input ConsensusInput)SignedReportCap { + + def := workflows.StepDefinition{ + ID: "offchain_reporting@1.0.0",Ref: ref, + Inputs: input.ToSteps(), + Config: map[string]any{ + "aggregation_config": cfg.AggregationConfig, + "aggregation_method": cfg.AggregationMethod, + "encoder": cfg.Encoder, + "encoder_config": cfg.EncoderConfig, + "report_id": cfg.ReportId, + }, + CapabilityType: capabilities.CapabilityTypeConsensus, + } + + + step := workflows.Step[SignedReport]{Definition: def} + return SignedReportCapFromStep(w, step) +} + + +type FeedValueCap interface { + workflows.CapDefinition[FeedValue] + Deviation() workflows.CapDefinition[string] + Heartbeat() workflows.CapDefinition[int] + RemappedID() workflows.CapDefinition[string] + private() +} + + +// FeedValueCapFromStep should only be called from generated code to assure type safety +func FeedValueCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[FeedValue]) FeedValueCap { + raw := step.AddTo(w) + return &feedValue{CapDefinition: raw} +} + + +type feedValue struct { + workflows.CapDefinition[FeedValue] +} + +func (*feedValue) private() {} +func (c *feedValue) Deviation() workflows.CapDefinition[string] { + return workflows.AccessField[FeedValue, string](c.CapDefinition, "Deviation") +} +func (c *feedValue) Heartbeat() workflows.CapDefinition[int] { + return workflows.AccessField[FeedValue, int](c.CapDefinition, "Heartbeat") +} +func (c *feedValue) RemappedID() workflows.CapDefinition[string] { + return workflows.AccessField[FeedValue, string](c.CapDefinition, "RemappedID") +} + +func NewFeedValueFromFields( + deviation workflows.CapDefinition[string], + heartbeat workflows.CapDefinition[int], + remappedID workflows.CapDefinition[string],) FeedValueCap { + return &simpleFeedValue{ + CapDefinition: workflows.ComponentCapDefinition[FeedValue]{ + "deviation": deviation.Ref(), + "heartbeat": heartbeat.Ref(), + "remappedID": remappedID.Ref(), + }, + deviation: deviation, + heartbeat: heartbeat, + remappedID: remappedID, + } +} + +type simpleFeedValue struct { + workflows.CapDefinition[FeedValue] + deviation workflows.CapDefinition[string] + heartbeat workflows.CapDefinition[int] + remappedID workflows.CapDefinition[string] +} +func (c *simpleFeedValue) Deviation() workflows.CapDefinition[string] { + return c.deviation +} +func (c *simpleFeedValue) Heartbeat() workflows.CapDefinition[int] { + return c.heartbeat +} +func (c *simpleFeedValue) RemappedID() workflows.CapDefinition[string] { + return c.remappedID +} + +func (c *simpleFeedValue) private() {} + + +type SignedReportCap interface { + workflows.CapDefinition[SignedReport] + Err() workflows.CapDefinition[bool] + Value() SignedReportValueCap + WorkflowExecutionID() workflows.CapDefinition[string] + private() +} + + +// SignedReportCapFromStep should only be called from generated code to assure type safety +func SignedReportCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[SignedReport]) SignedReportCap { + raw := step.AddTo(w) + return &signedReport{CapDefinition: raw} +} + + +type signedReport struct { + workflows.CapDefinition[SignedReport] +} + +func (*signedReport) private() {} +func (c *signedReport) Err() workflows.CapDefinition[bool] { + return workflows.AccessField[SignedReport, bool](c.CapDefinition, "Err") +} +func (c *signedReport) Value() SignedReportValueCap { + return &signedReportValue{ CapDefinition: workflows.AccessField[SignedReport, SignedReportValue](c.CapDefinition, "Value")} +} +func (c *signedReport) WorkflowExecutionID() workflows.CapDefinition[string] { + return workflows.AccessField[SignedReport, string](c.CapDefinition, "WorkflowExecutionID") +} + +func NewSignedReportFromFields( + err workflows.CapDefinition[bool], + value SignedReportValueCap, + workflowExecutionID workflows.CapDefinition[string],) SignedReportCap { + return &simpleSignedReport{ + CapDefinition: workflows.ComponentCapDefinition[SignedReport]{ + "err": err.Ref(), + "value": value.Ref(), + "workflowExecutionID": workflowExecutionID.Ref(), + }, + err: err, + value: value, + workflowExecutionID: workflowExecutionID, + } +} + +type simpleSignedReport struct { + workflows.CapDefinition[SignedReport] + err workflows.CapDefinition[bool] + value SignedReportValueCap + workflowExecutionID workflows.CapDefinition[string] +} +func (c *simpleSignedReport) Err() workflows.CapDefinition[bool] { + return c.err +} +func (c *simpleSignedReport) Value() SignedReportValueCap { + return c.value +} +func (c *simpleSignedReport) WorkflowExecutionID() workflows.CapDefinition[string] { + return c.workflowExecutionID +} + +func (c *simpleSignedReport) private() {} + + +type SignedReportValueCap interface { + workflows.CapDefinition[SignedReportValue] + Underlying() SignedReportValueUnderlyingCap + private() +} + + +// SignedReportValueCapFromStep should only be called from generated code to assure type safety +func SignedReportValueCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[SignedReportValue]) SignedReportValueCap { + raw := step.AddTo(w) + return &signedReportValue{CapDefinition: raw} +} + + +type signedReportValue struct { + workflows.CapDefinition[SignedReportValue] +} + +func (*signedReportValue) private() {} +func (c *signedReportValue) Underlying() SignedReportValueUnderlyingCap { + return SignedReportValueUnderlyingCap(workflows.AccessField[SignedReportValue, SignedReportValueUnderlying](c.CapDefinition, "Underlying")) +} + +func NewSignedReportValueFromFields( + underlying SignedReportValueUnderlyingCap,) SignedReportValueCap { + return &simpleSignedReportValue{ + CapDefinition: workflows.ComponentCapDefinition[SignedReportValue]{ + "underlying": underlying.Ref(), + }, + underlying: underlying, + } +} + +type simpleSignedReportValue struct { + workflows.CapDefinition[SignedReportValue] + underlying SignedReportValueUnderlyingCap +} +func (c *simpleSignedReportValue) Underlying() SignedReportValueUnderlyingCap { + return c.underlying +} + +func (c *simpleSignedReportValue) private() {} + + +type SignedReportValueUnderlyingCap workflows.CapDefinition[SignedReportValueUnderlying] + + +type ConsensusInput struct { + Observations workflows.CapDefinition[[][]streams.Feed] +} + +func (input ConsensusInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "observations": input.Observations.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/consensus/ocr3/ocr3_consensus_generated.go b/pkg/capabilities/consensus/ocr3/ocr3_consensus_generated.go new file mode 100644 index 000000000..b68442419 --- /dev/null +++ b/pkg/capabilities/consensus/ocr3/ocr3_consensus_generated.go @@ -0,0 +1,318 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package ocr3 + +import "encoding/json" +import "fmt" +import streams "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers/streams" +import "reflect" + +// OCR3 consensus exposed as a capability. +type Consensus struct { + // Config corresponds to the JSON schema field "config". + Config ConsensusConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs ConsensusInputs `json:"inputs" yaml:"inputs" mapstructure:"inputs"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs SignedReport `json:"outputs" yaml:"outputs" mapstructure:"outputs"` +} + +type ConsensusConfig struct { + // AggregationConfig corresponds to the JSON schema field "aggregation_config". + AggregationConfig ConsensusConfigAggregationConfig `json:"aggregation_config" yaml:"aggregation_config" mapstructure:"aggregation_config"` + + // AggregationMethod corresponds to the JSON schema field "aggregation_method". + AggregationMethod ConsensusConfigAggregationMethod `json:"aggregation_method" yaml:"aggregation_method" mapstructure:"aggregation_method"` + + // Encoder corresponds to the JSON schema field "encoder". + Encoder ConsensusConfigEncoder `json:"encoder" yaml:"encoder" mapstructure:"encoder"` + + // EncoderConfig corresponds to the JSON schema field "encoder_config". + EncoderConfig ConsensusConfigEncoderConfig `json:"encoder_config" yaml:"encoder_config" mapstructure:"encoder_config"` + + // ReportId corresponds to the JSON schema field "report_id". + ReportId string `json:"report_id" yaml:"report_id" mapstructure:"report_id"` +} + +type ConsensusConfigAggregationConfig struct { + // Allowed partial staleness as a number between 0 and 1. + AllowedPartialStaleness string `json:"allowedPartialStaleness" yaml:"allowedPartialStaleness" mapstructure:"allowedPartialStaleness"` + + // Feeds corresponds to the JSON schema field "feeds". + Feeds ConsensusConfigAggregationConfigFeeds `json:"feeds" yaml:"feeds" mapstructure:"feeds"` +} + +type ConsensusConfigAggregationConfigFeeds map[string]FeedValue + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfigAggregationConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["allowedPartialStaleness"]; raw != nil && !ok { + return fmt.Errorf("field allowedPartialStaleness in ConsensusConfigAggregationConfig: required") + } + if _, ok := raw["feeds"]; raw != nil && !ok { + return fmt.Errorf("field feeds in ConsensusConfigAggregationConfig: required") + } + type Plain ConsensusConfigAggregationConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusConfigAggregationConfig(plain) + return nil +} + +type ConsensusConfigAggregationMethod string + +const ConsensusConfigAggregationMethodDataFeeds ConsensusConfigAggregationMethod = "data_feeds" + +var enumValues_ConsensusConfigAggregationMethod = []interface{}{ + "data_feeds", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfigAggregationMethod) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ConsensusConfigAggregationMethod { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ConsensusConfigAggregationMethod, v) + } + *j = ConsensusConfigAggregationMethod(v) + return nil +} + +type ConsensusConfigEncoder string + +type ConsensusConfigEncoderConfig struct { + // The ABI for report encoding. + Abi string `json:"abi" yaml:"abi" mapstructure:"abi"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfigEncoderConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["abi"]; raw != nil && !ok { + return fmt.Errorf("field abi in ConsensusConfigEncoderConfig: required") + } + type Plain ConsensusConfigEncoderConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusConfigEncoderConfig(plain) + return nil +} + +const ConsensusConfigEncoderEVM ConsensusConfigEncoder = "EVM" + +var enumValues_ConsensusConfigEncoder = []interface{}{ + "EVM", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfigEncoder) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ConsensusConfigEncoder { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ConsensusConfigEncoder, v) + } + *j = ConsensusConfigEncoder(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["aggregation_config"]; raw != nil && !ok { + return fmt.Errorf("field aggregation_config in ConsensusConfig: required") + } + if _, ok := raw["aggregation_method"]; raw != nil && !ok { + return fmt.Errorf("field aggregation_method in ConsensusConfig: required") + } + if _, ok := raw["encoder"]; raw != nil && !ok { + return fmt.Errorf("field encoder in ConsensusConfig: required") + } + if _, ok := raw["encoder_config"]; raw != nil && !ok { + return fmt.Errorf("field encoder_config in ConsensusConfig: required") + } + if _, ok := raw["report_id"]; raw != nil && !ok { + return fmt.Errorf("field report_id in ConsensusConfig: required") + } + type Plain ConsensusConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusConfig(plain) + return nil +} + +type ConsensusInputs struct { + // Observations corresponds to the JSON schema field "observations". + Observations [][]streams.Feed `json:"observations" yaml:"observations" mapstructure:"observations"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ConsensusInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["observations"]; raw != nil && !ok { + return fmt.Errorf("field observations in ConsensusInputs: required") + } + type Plain ConsensusInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ConsensusInputs(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Consensus) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Consensus: required") + } + if _, ok := raw["inputs"]; raw != nil && !ok { + return fmt.Errorf("field inputs in Consensus: required") + } + if _, ok := raw["outputs"]; raw != nil && !ok { + return fmt.Errorf("field outputs in Consensus: required") + } + type Plain Consensus + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Consensus(plain) + return nil +} + +type FeedValue struct { + // The deviation that is required to generate a new report. Expressed as a + // percentage. For example, 0.01 is 1% deviation. + Deviation string `json:"deviation" yaml:"deviation" mapstructure:"deviation"` + + // The interval in seconds after which a new report is generated, regardless of + // whether any deviations have occurred. New reports reset the timer. + Heartbeat int `json:"heartbeat" yaml:"heartbeat" mapstructure:"heartbeat"` + + // An optional remapped ID for the feed. + RemappedID *string `json:"remappedID,omitempty" yaml:"remappedID,omitempty" mapstructure:"remappedID,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *FeedValue) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["deviation"]; raw != nil && !ok { + return fmt.Errorf("field deviation in FeedValue: required") + } + if _, ok := raw["heartbeat"]; raw != nil && !ok { + return fmt.Errorf("field heartbeat in FeedValue: required") + } + type Plain FeedValue + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = FeedValue(plain) + return nil +} + +type SignedReport struct { + // Err corresponds to the JSON schema field "Err". + Err bool `json:"Err" yaml:"Err" mapstructure:"Err"` + + // Value corresponds to the JSON schema field "Value". + Value SignedReportValue `json:"Value" yaml:"Value" mapstructure:"Value"` + + // WorkflowExecutionID corresponds to the JSON schema field "WorkflowExecutionID". + WorkflowExecutionID string `json:"WorkflowExecutionID" yaml:"WorkflowExecutionID" mapstructure:"WorkflowExecutionID"` +} + +type SignedReportValue struct { + // Underlying corresponds to the JSON schema field "Underlying". + Underlying SignedReportValueUnderlying `json:"Underlying" yaml:"Underlying" mapstructure:"Underlying"` +} + +type SignedReportValueUnderlying map[string]interface{} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SignedReportValue) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["Underlying"]; raw != nil && !ok { + return fmt.Errorf("field Underlying in SignedReportValue: required") + } + type Plain SignedReportValue + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SignedReportValue(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SignedReport) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["Err"]; raw != nil && !ok { + return fmt.Errorf("field Err in SignedReport: required") + } + if _, ok := raw["Value"]; raw != nil && !ok { + return fmt.Errorf("field Value in SignedReport: required") + } + if _, ok := raw["WorkflowExecutionID"]; raw != nil && !ok { + return fmt.Errorf("field WorkflowExecutionID in SignedReport: required") + } + type Plain SignedReport + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SignedReport(plain) + return nil +} diff --git a/pkg/capabilities/gen.go b/pkg/capabilities/gen.go new file mode 100644 index 000000000..2b625d386 --- /dev/null +++ b/pkg/capabilities/gen.go @@ -0,0 +1,3 @@ +package capabilities + +//go:generate go run github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli generate-types --dir $GOFILE diff --git a/pkg/capabilities/targets/chainwriter/chainwriter_target_generated.go b/pkg/capabilities/targets/chainwriter/chainwriter_target_generated.go new file mode 100644 index 000000000..7fbd1cce5 --- /dev/null +++ b/pkg/capabilities/targets/chainwriter/chainwriter_target_generated.go @@ -0,0 +1,127 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package chainwriter + +import "encoding/json" +import "fmt" +import ocr3 "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" +import "reflect" + +// Writes to a blockchain +type Target struct { + // Config corresponds to the JSON schema field "config". + Config TargetConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Inputs corresponds to the JSON schema field "inputs". + Inputs TargetInputs `json:"inputs" yaml:"inputs" mapstructure:"inputs"` +} + +type TargetConfig struct { + // The address to write to. + Address string `json:"address" yaml:"address" mapstructure:"address"` + + // The delta stage which must be a number followed by a time symbol (s for + // seconds, m for minutes, h for hours, d for days). + DeltaStage string `json:"deltaStage" yaml:"deltaStage" mapstructure:"deltaStage"` + + // The schedule which must be the string 'oneAtATime'. + Schedule TargetConfigSchedule `json:"schedule" yaml:"schedule" mapstructure:"schedule"` +} + +type TargetConfigSchedule string + +const TargetConfigScheduleAllAtOnce TargetConfigSchedule = "allAtOnce" +const TargetConfigScheduleOneAtATime TargetConfigSchedule = "oneAtATime" + +var enumValues_TargetConfigSchedule = []interface{}{ + "oneAtATime", + "allAtOnce", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TargetConfigSchedule) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_TargetConfigSchedule { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_TargetConfigSchedule, v) + } + *j = TargetConfigSchedule(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TargetConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["address"]; raw != nil && !ok { + return fmt.Errorf("field address in TargetConfig: required") + } + if _, ok := raw["deltaStage"]; raw != nil && !ok { + return fmt.Errorf("field deltaStage in TargetConfig: required") + } + if _, ok := raw["schedule"]; raw != nil && !ok { + return fmt.Errorf("field schedule in TargetConfig: required") + } + type Plain TargetConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TargetConfig(plain) + return nil +} + +type TargetInputs struct { + // SignedReport corresponds to the JSON schema field "signed_report". + SignedReport ocr3.SignedReport `json:"signed_report" yaml:"signed_report" mapstructure:"signed_report"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TargetInputs) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["signed_report"]; raw != nil && !ok { + return fmt.Errorf("field signed_report in TargetInputs: required") + } + type Plain TargetInputs + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = TargetInputs(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Target) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Target: required") + } + if _, ok := raw["inputs"]; raw != nil && !ok { + return fmt.Errorf("field inputs in Target: required") + } + type Plain Target + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Target(plain) + return nil +} diff --git a/pkg/capabilities/targets/chainwriter/target_builders_generated.go b/pkg/capabilities/targets/chainwriter/target_builders_generated.go new file mode 100644 index 000000000..7ad7f1f03 --- /dev/null +++ b/pkg/capabilities/targets/chainwriter/target_builders_generated.go @@ -0,0 +1,42 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package chainwriter + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" + ocr3 "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3" +) + + + +func (cfg TargetConfig) New(w *workflows.WorkflowSpecFactory,id string, input TargetInput) { + + def := workflows.StepDefinition{ + ID: id, + Inputs: input.ToSteps(), + Config: map[string]any{ + "address": cfg.Address, + "deltaStage": cfg.DeltaStage, + "schedule": cfg.Schedule, + }, + CapabilityType: capabilities.CapabilityTypeTarget, + } + + + step := workflows.Step[struct{}]{Definition: def} + step.AddTo(w) +} + + +type TargetInput struct { + SignedReport workflows.CapDefinition[ocr3.SignedReport] +} + +func (input TargetInput) ToSteps() workflows.StepInputs { + return workflows.StepInputs{ + Mapping: map[string]any{ + "signed_report": input.SignedReport.Ref(), + }, + } +} \ No newline at end of file diff --git a/pkg/capabilities/triggers/mercury_trigger.go b/pkg/capabilities/triggers/mercury_trigger.go index d4a095869..e1822ad59 100644 --- a/pkg/capabilities/triggers/mercury_trigger.go +++ b/pkg/capabilities/triggers/mercury_trigger.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/capabilities/datastreams" + "github.com/smartcontractkit/chainlink-common/pkg/capabilities/triggers/streams" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/values" @@ -27,20 +28,13 @@ const defaultTickerResolutionMs = 1000 // TODO pending capabilities configuration implementation - this should be configurable with a sensible default const defaultSendChannelBufferSize = 1000 -type config struct { - // strings should be hex-encoded 32-byte values, prefixed with "0x", all lowercase, minimum 1 item - FeedIDs []string `json:"feedIds" jsonschema:"pattern=^0x[0-9a-f]{64}$,minItems=1"` - // must be greater than 0 - MaxFrequencyMs int `json:"maxFrequencyMs" jsonschema:"minimum=1"` -} - type inputs struct { TriggerID string `json:"triggerId"` } // This Trigger Service allows for the registration and deregistration of triggers. You can also send reports to the service. type MercuryTriggerService struct { - capabilities.Validator[config, inputs, capabilities.TriggerEvent] + capabilities.Validator[streams.TriggerConfig, inputs, capabilities.TriggerEvent] capabilities.CapabilityInfo tickerResolutionMs int64 subscribers map[string]*subscriber @@ -57,7 +51,7 @@ var _ services.Service = &MercuryTriggerService{} type subscriber struct { ch chan<- capabilities.CapabilityResponse workflowID string - config config + config streams.TriggerConfig } // Mercury Trigger will send events to each subscriber every MaxFrequencyMs (configurable per subscriber). @@ -68,7 +62,7 @@ func NewMercuryTriggerService(tickerResolutionMs int64, lggr logger.Logger) *Mer tickerResolutionMs = defaultTickerResolutionMs } return &MercuryTriggerService{ - Validator: capabilities.NewValidator[config, inputs, capabilities.TriggerEvent](capabilities.ValidatorArgs{Info: capInfo}), + Validator: capabilities.NewValidator[streams.TriggerConfig, inputs, capabilities.TriggerEvent](capabilities.ValidatorArgs{Info: capInfo}), CapabilityInfo: capInfo, tickerResolutionMs: tickerResolutionMs, subscribers: make(map[string]*subscriber), @@ -188,7 +182,7 @@ func (o *MercuryTriggerService) process(timestamp int64) { for _, sub := range o.subscribers { if timestamp%int64(sub.config.MaxFrequencyMs) == 0 { reportList := make([]datastreams.FeedReport, 0) - for _, feedID := range sub.config.FeedIDs { + for _, feedID := range sub.config.FeedIds { if latest, ok := o.latestReports[datastreams.FeedID(feedID)]; ok { reportList = append(reportList, latest) } diff --git a/pkg/capabilities/triggers/streams/streams_trigger_generated.go b/pkg/capabilities/triggers/streams/streams_trigger_generated.go new file mode 100644 index 000000000..5e630ea13 --- /dev/null +++ b/pkg/capabilities/triggers/streams/streams_trigger_generated.go @@ -0,0 +1,129 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package streams + +import "encoding/json" +import "fmt" + +type Feed struct { + // This value is extracted from the fullReport. Benchmark price represented as + // bytes encoded as base64 string. + BenchmarkPrice string `json:"benchmarkPrice" yaml:"benchmarkPrice" mapstructure:"benchmarkPrice"` + + // FeedId corresponds to the JSON schema field "feedId". + FeedId FeedId `json:"feedId" yaml:"feedId" mapstructure:"feedId"` + + // Full report represented as bytes encoded as base64 string. + FullReport string `json:"fullReport" yaml:"fullReport" mapstructure:"fullReport"` + + // This value is extracted from the fullReport. A unix timestamp represented as an + // int64 value. Timestamp is captured at the time of report creation. + ObservationTimestamp int `json:"observationTimestamp" yaml:"observationTimestamp" mapstructure:"observationTimestamp"` + + // Report context represented as bytes encoded as base64 string. This is required + // to validate the signatures. + ReportContext string `json:"reportContext" yaml:"reportContext" mapstructure:"reportContext"` + + // Signature over full report and report context represented as bytes encoded as + // base64 string. + Signatures []string `json:"signatures" yaml:"signatures" mapstructure:"signatures"` +} + +// The ID of the data feed. +type FeedId string + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Feed) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["benchmarkPrice"]; raw != nil && !ok { + return fmt.Errorf("field benchmarkPrice in Feed: required") + } + if _, ok := raw["feedId"]; raw != nil && !ok { + return fmt.Errorf("field feedId in Feed: required") + } + if _, ok := raw["fullReport"]; raw != nil && !ok { + return fmt.Errorf("field fullReport in Feed: required") + } + if _, ok := raw["observationTimestamp"]; raw != nil && !ok { + return fmt.Errorf("field observationTimestamp in Feed: required") + } + if _, ok := raw["reportContext"]; raw != nil && !ok { + return fmt.Errorf("field reportContext in Feed: required") + } + if _, ok := raw["signatures"]; raw != nil && !ok { + return fmt.Errorf("field signatures in Feed: required") + } + type Plain Feed + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.Signatures != nil && len(plain.Signatures) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "signatures", 1) + } + *j = Feed(plain) + return nil +} + +// Streams Trigger +type Trigger struct { + // Config corresponds to the JSON schema field "config". + Config TriggerConfig `json:"config" yaml:"config" mapstructure:"config"` + + // Outputs corresponds to the JSON schema field "outputs". + Outputs []Feed `json:"outputs,omitempty" yaml:"outputs,omitempty" mapstructure:"outputs,omitempty"` +} + +type TriggerConfig struct { + // The IDs of the data feeds that will have their reports included in the trigger + // event. + FeedIds []FeedId `json:"feedIds" yaml:"feedIds" mapstructure:"feedIds"` + + // The interval in seconds after which a new trigger event is generated. + MaxFrequencyMs int `json:"maxFrequencyMs" yaml:"maxFrequencyMs" mapstructure:"maxFrequencyMs"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *TriggerConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["feedIds"]; raw != nil && !ok { + return fmt.Errorf("field feedIds in TriggerConfig: required") + } + if _, ok := raw["maxFrequencyMs"]; raw != nil && !ok { + return fmt.Errorf("field maxFrequencyMs in TriggerConfig: required") + } + type Plain TriggerConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if plain.FeedIds != nil && len(plain.FeedIds) < 1 { + return fmt.Errorf("field %s length: must be >= %d", "feedIds", 1) + } + *j = TriggerConfig(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Trigger) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["config"]; raw != nil && !ok { + return fmt.Errorf("field config in Trigger: required") + } + type Plain Trigger + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Trigger(plain) + return nil +} diff --git a/pkg/capabilities/triggers/streams/trigger_builders_generated.go b/pkg/capabilities/triggers/streams/trigger_builders_generated.go new file mode 100644 index 000000000..32e4ed770 --- /dev/null +++ b/pkg/capabilities/triggers/streams/trigger_builders_generated.go @@ -0,0 +1,130 @@ +// Code generated by github.com/smartcontractkit/chainlink-common/pkg/capabilities/cli, DO NOT EDIT. + +package streams + +import ( + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" + "github.com/smartcontractkit/chainlink-common/pkg/workflows" +) + + + +func (cfg TriggerConfig) New(w *workflows.WorkflowSpecFactory,)workflows.CapDefinition[[]Feed] { + ref := "trigger" + def := workflows.StepDefinition{ + ID: "streams-trigger@1.0.0",Ref: ref, + Inputs: workflows.StepInputs{} , + Config: map[string]any{ + "feedIds": cfg.FeedIds, + "maxFrequencyMs": cfg.MaxFrequencyMs, + }, + CapabilityType: capabilities.CapabilityTypeTrigger, + } + + + step := workflows.Step[[]Feed]{Definition: def} + return step.AddTo(w) +} + + +type FeedCap interface { + workflows.CapDefinition[Feed] + BenchmarkPrice() workflows.CapDefinition[string] + FeedId() FeedIdCap + FullReport() workflows.CapDefinition[string] + ObservationTimestamp() workflows.CapDefinition[int] + ReportContext() workflows.CapDefinition[string] + Signatures() workflows.CapDefinition[[]string] + private() +} + + +// FeedCapFromStep should only be called from generated code to assure type safety +func FeedCapFromStep(w *workflows.WorkflowSpecFactory, step workflows.Step[Feed]) FeedCap { + raw := step.AddTo(w) + return &feed{CapDefinition: raw} +} + + +type feed struct { + workflows.CapDefinition[Feed] +} + +func (*feed) private() {} +func (c *feed) BenchmarkPrice() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "BenchmarkPrice") +} +func (c *feed) FeedId() FeedIdCap { + return FeedIdCap(workflows.AccessField[Feed, FeedId](c.CapDefinition, "FeedId")) +} +func (c *feed) FullReport() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "FullReport") +} +func (c *feed) ObservationTimestamp() workflows.CapDefinition[int] { + return workflows.AccessField[Feed, int](c.CapDefinition, "ObservationTimestamp") +} +func (c *feed) ReportContext() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "ReportContext") +} +func (c *feed) Signatures() workflows.CapDefinition[[]string] { + return workflows.AccessField[Feed, []string](c.CapDefinition, "Signatures") +} + +func NewFeedFromFields( + benchmarkPrice workflows.CapDefinition[string], + feedId FeedIdCap, + fullReport workflows.CapDefinition[string], + observationTimestamp workflows.CapDefinition[int], + reportContext workflows.CapDefinition[string], + signatures workflows.CapDefinition[[]string],) FeedCap { + return &simpleFeed{ + CapDefinition: workflows.ComponentCapDefinition[Feed]{ + "benchmarkPrice": benchmarkPrice.Ref(), + "feedId": feedId.Ref(), + "fullReport": fullReport.Ref(), + "observationTimestamp": observationTimestamp.Ref(), + "reportContext": reportContext.Ref(), + "signatures": signatures.Ref(), + }, + benchmarkPrice: benchmarkPrice, + feedId: feedId, + fullReport: fullReport, + observationTimestamp: observationTimestamp, + reportContext: reportContext, + signatures: signatures, + } +} + +type simpleFeed struct { + workflows.CapDefinition[Feed] + benchmarkPrice workflows.CapDefinition[string] + feedId FeedIdCap + fullReport workflows.CapDefinition[string] + observationTimestamp workflows.CapDefinition[int] + reportContext workflows.CapDefinition[string] + signatures workflows.CapDefinition[[]string] +} +func (c *simpleFeed) BenchmarkPrice() workflows.CapDefinition[string] { + return c.benchmarkPrice +} +func (c *simpleFeed) FeedId() FeedIdCap { + return c.feedId +} +func (c *simpleFeed) FullReport() workflows.CapDefinition[string] { + return c.fullReport +} +func (c *simpleFeed) ObservationTimestamp() workflows.CapDefinition[int] { + return c.observationTimestamp +} +func (c *simpleFeed) ReportContext() workflows.CapDefinition[string] { + return c.reportContext +} +func (c *simpleFeed) Signatures() workflows.CapDefinition[[]string] { + return c.signatures +} + +func (c *simpleFeed) private() {} + + +type FeedIdCap workflows.CapDefinition[FeedId] + diff --git a/pkg/workflows/builder.go b/pkg/workflows/builder.go new file mode 100644 index 000000000..e7b2b4316 --- /dev/null +++ b/pkg/workflows/builder.go @@ -0,0 +1,37 @@ +package workflows + +// The real implementations will come in a follow-up PR. +// these stubs allow the code from the generators to compile. +// A holistic view can be seen at https://github.com/smartcontractkit/chainlink-common/pull/695 + +type WorkflowSpecFactory struct{} + +type CapDefinition[O any] interface { + Ref() any + self() CapDefinition[O] +} + +type Step[O any] struct { + Definition StepDefinition +} + +// AddTo is meant to be called by generated code +func (step *Step[O]) AddTo(_ *WorkflowSpecFactory) CapDefinition[O] { + panic("TODO: implement") +} + +// AccessField is meant to be used by generated code +func AccessField[I, O any](_ CapDefinition[I], _ string) CapDefinition[O] { + panic("TODO: implement") +} + +// ComponentCapDefinition is meant to be used by generated code +type ComponentCapDefinition[O any] map[string]any + +func (ComponentCapDefinition[O]) Ref() any { + panic("TODO: implement") +} + +func (c ComponentCapDefinition[O]) self() CapDefinition[O] { + return c +}