diff --git a/go.mod b/go.mod index 94065c9cb5..adf27de94c 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.1-0.20240808181525-7f5717483254 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 @@ -35,13 +40,22 @@ require ( go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.25.0 - golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa gonum.org/v1/gonum v0.15.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 sigs.k8s.io/yaml v1.4.0 ) +require ( + github.com/goccy/go-yaml v1.12.0 // 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 @@ -78,7 +92,7 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect diff --git a/go.sum b/go.sum index 3b3797a39d..d7845f8ae2 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,10 @@ 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/atombender/go-jsonschema v0.16.1-0.20240808181525-7f5717483254 h1:SNnOpkny6Ra4Cz486bOyh5OOU8xO8c+/YPAtQyY9XEk= +github.com/atombender/go-jsonschema v0.16.1-0.20240808181525-7f5717483254/go.mod h1:5I/61gtE016eXb9Xz5JSsE0vUD9WXIWPg6eko1+zrOg= 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 +33,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 +54,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 +65,18 @@ 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/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/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 +124,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 +144,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 +162,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 +196,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 +213,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 +229,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= @@ -255,6 +288,8 @@ golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA= golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -283,6 +318,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -299,6 +335,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 +359,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 0000000000..57864cbb82 --- /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 0000000000..8d519a81ee --- /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 0000000000..ca9297ec8a --- /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 0000000000..87f8df83ca --- /dev/null +++ b/pkg/capabilities/cli/cmd/generate_types.go @@ -0,0 +1,218 @@ +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" +) + +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 + } + + capabilityTypeRaw := cfgInfo.SchemaToTypeInfo[schemaPath].CapabilityTypeRaw + capabilityType := capabilityTypeFromString(capabilityTypeRaw) + if err = capabilityType.IsValid(); err != nil { + return fmt.Errorf("invalid capability type %v", capabilityTypeRaw) + } + + 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{ + CapabilityTypeRaw: 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.CapabilityTypeRaw + outputName := strings.Replace(schemaFilePath, capabilityType+"-schema.json", 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 0000000000..5d1493c9be --- /dev/null +++ b/pkg/capabilities/cli/cmd/generated_info.go @@ -0,0 +1,153 @@ +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 { + return g.Types[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: capabilityTypeFromString(typeInfo.CapabilityTypeRaw), + Input: input, + ExtraImports: extraImports, + ID: capID, + }, nil +} + +func extractInputAndConfig(generatedStructs map[string]Struct, typeInfo TypeInfo, root Struct) (*Struct, Struct) { + delete(generatedStructs, typeInfo.RootType) + configType := root.Outputs["Config"].Type + inputField, ok := root.Outputs["Inputs"] + var input *Struct + if ok { + inputType := inputField.Type + inputS, ok := generatedStructs[inputType] + if ok { + input = &inputS + delete(generatedStructs, inputType) + } + } + config := generatedStructs[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:] + } + + if strings.HasPrefix(f.Type, "*") { + f.Type = f.Type[1:] + } + + 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 +} diff --git a/pkg/capabilities/cli/cmd/root_cmd.go b/pkg/capabilities/cli/cmd/root_cmd.go new file mode 100644 index 0000000000..34f1dbc9fd --- /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 0000000000..a37649bb81 --- /dev/null +++ b/pkg/capabilities/cli/cmd/struct.go @@ -0,0 +1,6 @@ +package cmd + +type Struct struct { + Name string + Outputs map[string]Field +} 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 0000000000..86bbc55e0b --- /dev/null +++ b/pkg/capabilities/cli/cmd/template_workflow_generator_helper.go @@ -0,0 +1,72 @@ +package cmd + +import ( + "bytes" + _ "embed" + "strings" + "text/template" + + "github.com/iancoleman/strcase" +) + +//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, + "ToSnake": strcase.ToSnake, + "ConvertToBaseIfFirstOutput": func(s string) string { + if info.RootOutput == s { + return info.BaseName + } + return s + }, + "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 0000000000..7bdc22ae99 --- /dev/null +++ b/pkg/capabilities/cli/cmd/templates/go_workflow_builder.go.tmpl @@ -0,0 +1,127 @@ +// 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 }} +) + +func (cfg {{.Config.Name}}) New(w *workflows.WorkflowSpecFactory, {{- if not .ID }}id string,{{- end }} {{- if and (ne .CapabilityType.String "target") (ne .CapabilityType.String "trigger")}}ref string,{{- end }}{{- if .Input }} input {{InputAfterCapability}}{{- end }}) {{- if ne .CapabilityType.String "target"}}{{- if eq .RootNumSlice 0}}{{.RootType.Name|ConvertToBaseIfFirstOutput}}Cap{{- else }}workflows.CapDefinition[{{Repeat "[]" .RootNumSlice}}{{.RootType.Name}}]{{- end }}{{- end }} { + {{ if eq .CapabilityType.String "trigger" }} ref := "trigger" {{- end }} + def := workflows.StepDefinition{ + ID: {{- if .ID }} "{{.ID}}" {{- else }} id {{- end }}, + {{- if ne .CapabilityType.String "target"}}Ref: ref, {{- end }} + Inputs: workflows.StepInputs{ +{{- if .Input }} + Mapping: map[string]any{ + {{- range $fieldName, $type := .Input.Outputs }} + "{{$type.ConfigName}}": input.{{$fieldName}}.Ref(), + {{- end }} + }, +{{- end }} + }, + Config: map[string]any{ +{{- range $fieldName, $type := .Config.Outputs }} + "{{$type.ConfigName}}": cfg.{{$fieldName}}, +{{- end }} + }, + CapabilityType: capabilities.CapabilityType{{.CapabilityType.String|Capitalize}}, + } + step := workflows.Step[{{- if eq .CapabilityType.String "target"}}struct{}{{- else }}{{Repeat "[]" .RootNumSlice}}{{.RootType.Name}} {{- end}}]{Definition: def} + {{if ne .CapabilityType.String "target"}} raw := {{- end }} step.AddTo(w) + {{if ne .CapabilityType.String "target"}} return {{- if eq 0 .RootNumSlice }} &{{.BaseName|LowerFirst}}{CapDefinition: raw} {{- else }} raw {{- end }} {{- end }} +} + +{{ range $key, $value := .Types }} +{{- if .Outputs }} +type {{$key|ConvertToBaseIfFirstOutput}}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() +} + +type {{$key|ConvertToBaseIfFirstOutput|LowerFirst}} struct { + workflows.CapDefinition[{{ $key }}] +} + +func (*{{$key|ConvertToBaseIfFirstOutput|LowerFirst}}) private() {} + + {{- range $fieldName, $type := .Outputs }} + {{- if or $type.IsPrimitive }} +func (c *{{$key|ConvertToBaseIfFirstOutput|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|ConvertToBaseIfFirstOutput|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|ConvertToBaseIfFirstOutput}}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|ConvertToBaseIfFirstOutput}}Cap { + return &simple{{$key|ConvertToBaseIfFirstOutput}}{ + 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|ConvertToBaseIfFirstOutput}} 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|ConvertToBaseIfFirstOutput}}) {{$fieldName}}() workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] { + {{- else }} +func (c *simple{{$key|ConvertToBaseIfFirstOutput}}) {{$fieldName}}() {{ $type.Type }}Cap { + {{- end }} + return c.{{$fieldName|LowerFirst}} +} + {{- end }} + +func (c *simple{{$key|ConvertToBaseIfFirstOutput}}) private() {} +{{- else }} +type {{$key|ConvertToBaseIfFirstOutput}}Cap workflows.CapDefinition[{{ $key }}] +{{- end }} + +{{ end }} + +{{- if .Input }} +type {{InputAfterCapability}} struct { +{{- range $fieldName, $type := .Input.Outputs }} + {{$fieldName}} workflows.CapDefinition[{{Repeat "[]" $type.NumSlice}}{{ $type.Type }}] +{{- end }} +} +{{- end }} \ No newline at end of file diff --git a/pkg/capabilities/cli/cmd/type_info.go b/pkg/capabilities/cli/cmd/type_info.go new file mode 100644 index 0000000000..a8b35de15a --- /dev/null +++ b/pkg/capabilities/cli/cmd/type_info.go @@ -0,0 +1,7 @@ +package cmd + +type TypeInfo struct { + CapabilityTypeRaw string + 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 0000000000..02e27073cb --- /dev/null +++ b/pkg/capabilities/cli/cmd/utils.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "os" + "path" + "strings" + + "github.com/smartcontractkit/chainlink-common/pkg/capabilities" +) + +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 capabilityTypeFromString(capabilityTypeRaw string) capabilities.CapabilityType { + var capabilityType capabilities.CapabilityType + for ; capabilityType.IsValid() == nil; capabilityType++ { + if capabilityType.String() == capabilityTypeRaw { + return capabilityType + } + } + + return capabilityType +} + +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 0000000000..5e10c3efa9 --- /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 0000000000..bc8cc38688 --- /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 0000000000..73177cf705 --- /dev/null +++ b/pkg/capabilities/consensus/ocr3/consensus_builders_generated.go @@ -0,0 +1,192 @@ +// 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)ConsensusCap { + + def := workflows.StepDefinition{ + ID: "offchain_reporting@1.0.0",Ref: ref, + Inputs: workflows.StepInputs{ + Mapping: map[string]any{ + "observations": input.Observations.Ref(), + }, + }, + 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} + raw := step.AddTo(w) + return &consensus{CapDefinition: raw} +} + + +type FeedValueCap interface { + workflows.CapDefinition[FeedValue] + Deviation() workflows.CapDefinition[string] + Heartbeat() workflows.CapDefinition[int] + RemappedID() workflows.CapDefinition[string] + private() +} + +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 ConsensusCap interface { + workflows.CapDefinition[SignedReport] + Err() workflows.CapDefinition[bool] + Value() SignedReportValueCap + WorkflowExecutionID() workflows.CapDefinition[string] + private() +} + +type consensus struct { + workflows.CapDefinition[SignedReport] +} + +func (*consensus) private() {} +func (c *consensus) Err() workflows.CapDefinition[bool] { + return workflows.AccessField[SignedReport, bool](c.CapDefinition, "Err") +} +func (c *consensus) Value() SignedReportValueCap { + return &signedReportValue{ CapDefinition: workflows.AccessField[SignedReport, SignedReportValue](c.CapDefinition, "Value")} +} +func (c *consensus) WorkflowExecutionID() workflows.CapDefinition[string] { + return workflows.AccessField[SignedReport, string](c.CapDefinition, "WorkflowExecutionID") +} + +func NewConsensusFromFields( + err workflows.CapDefinition[bool], + value SignedReportValueCap, + workflowExecutionID workflows.CapDefinition[string],) ConsensusCap { + return &simpleConsensus{ + CapDefinition: workflows.ComponentCapDefinition[SignedReport]{ + "err": err.Ref(), + "value": value.Ref(), + "workflowExecutionID": workflowExecutionID.Ref(), + }, + err: err, + value: value, + workflowExecutionID: workflowExecutionID, + } +} + +type simpleConsensus struct { + workflows.CapDefinition[SignedReport] + err workflows.CapDefinition[bool] + value SignedReportValueCap + workflowExecutionID workflows.CapDefinition[string] +} +func (c *simpleConsensus) Err() workflows.CapDefinition[bool] { + return c.err +} +func (c *simpleConsensus) Value() SignedReportValueCap { + return c.value +} +func (c *simpleConsensus) WorkflowExecutionID() workflows.CapDefinition[string] { + return c.workflowExecutionID +} + +func (c *simpleConsensus) private() {} + + +type SignedReportValueCap interface { + workflows.CapDefinition[SignedReportValue] + Underlying() SignedReportValueUnderlyingCap + private() +} + +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] +} \ 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 0000000000..b68442419a --- /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 0000000000..2b625d386e --- /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 0000000000..a47fece9c8 --- /dev/null +++ b/pkg/capabilities/targets/chainwriter/chainwriter_target_generated.go @@ -0,0 +1,125 @@ +// 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 TargetConfigScheduleOneAtATime TargetConfigSchedule = "oneAtATime" + +var enumValues_TargetConfigSchedule = []interface{}{ + "oneAtATime", +} + +// 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 0000000000..adba0deaf9 --- /dev/null +++ b/pkg/capabilities/targets/chainwriter/target_builders_generated.go @@ -0,0 +1,35 @@ +// 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: workflows.StepInputs{ + Mapping: map[string]any{ + "signed_report": input.SignedReport.Ref(), + }, + }, + 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] +} \ No newline at end of file diff --git a/pkg/capabilities/triggers/mercury_trigger.go b/pkg/capabilities/triggers/mercury_trigger.go index d4a0958691..63be09888f 100644 --- a/pkg/capabilities/triggers/mercury_trigger.go +++ b/pkg/capabilities/triggers/mercury_trigger.go @@ -27,7 +27,7 @@ const defaultTickerResolutionMs = 1000 // TODO pending capabilities configuration implementation - this should be configurable with a sensible default const defaultSendChannelBufferSize = 1000 -type config struct { +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 @@ -40,7 +40,7 @@ type inputs struct { // 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[Config, inputs, capabilities.TriggerEvent] capabilities.CapabilityInfo tickerResolutionMs int64 subscribers map[string]*subscriber @@ -57,7 +57,7 @@ var _ services.Service = &MercuryTriggerService{} type subscriber struct { ch chan<- capabilities.CapabilityResponse workflowID string - config config + config Config } // Mercury Trigger will send events to each subscriber every MaxFrequencyMs (configurable per subscriber). @@ -68,7 +68,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[Config, inputs, capabilities.TriggerEvent](capabilities.ValidatorArgs{Info: capInfo}), CapabilityInfo: capInfo, tickerResolutionMs: tickerResolutionMs, subscribers: make(map[string]*subscriber), 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 0000000000..5e630ea13c --- /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 0000000000..07f3743549 --- /dev/null +++ b/pkg/capabilities/triggers/streams/trigger_builders_generated.go @@ -0,0 +1,120 @@ +// 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} + raw := step.AddTo(w) + return raw +} + + +type TriggerCap 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() +} + +type trigger struct { + workflows.CapDefinition[Feed] +} + +func (*trigger) private() {} +func (c *trigger) BenchmarkPrice() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "BenchmarkPrice") +} +func (c *trigger) FeedId() FeedIdCap { + return FeedIdCap(workflows.AccessField[Feed, FeedId](c.CapDefinition, "FeedId")) +} +func (c *trigger) FullReport() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "FullReport") +} +func (c *trigger) ObservationTimestamp() workflows.CapDefinition[int] { + return workflows.AccessField[Feed, int](c.CapDefinition, "ObservationTimestamp") +} +func (c *trigger) ReportContext() workflows.CapDefinition[string] { + return workflows.AccessField[Feed, string](c.CapDefinition, "ReportContext") +} +func (c *trigger) Signatures() workflows.CapDefinition[[]string] { + return workflows.AccessField[Feed, []string](c.CapDefinition, "Signatures") +} + +func NewTriggerFromFields( + benchmarkPrice workflows.CapDefinition[string], + feedId FeedIdCap, + fullReport workflows.CapDefinition[string], + observationTimestamp workflows.CapDefinition[int], + reportContext workflows.CapDefinition[string], + signatures workflows.CapDefinition[[]string],) TriggerCap { + return &simpleTrigger{ + 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 simpleTrigger 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 *simpleTrigger) BenchmarkPrice() workflows.CapDefinition[string] { + return c.benchmarkPrice +} +func (c *simpleTrigger) FeedId() FeedIdCap { + return c.feedId +} +func (c *simpleTrigger) FullReport() workflows.CapDefinition[string] { + return c.fullReport +} +func (c *simpleTrigger) ObservationTimestamp() workflows.CapDefinition[int] { + return c.observationTimestamp +} +func (c *simpleTrigger) ReportContext() workflows.CapDefinition[string] { + return c.reportContext +} +func (c *simpleTrigger) Signatures() workflows.CapDefinition[[]string] { + return c.signatures +} + +func (c *simpleTrigger) private() {} + + +type FeedIdCap workflows.CapDefinition[FeedId] + diff --git a/pkg/workflows/builder.go b/pkg/workflows/builder.go new file mode 100644 index 0000000000..6431c481a1 --- /dev/null +++ b/pkg/workflows/builder.go @@ -0,0 +1,32 @@ +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[T any] interface { + Ref() any +} + +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") +}