From 6b8031ce09c7033566f02ffb5fd71aff37352d06 Mon Sep 17 00:00:00 2001 From: JP Robinson Date: Tue, 18 Jul 2017 22:57:05 -0400 Subject: [PATCH 1/7] adding kit/datastore reading-list example to mimic marvin --- examples/servers/reading-list/.drone.yml | 58 + examples/servers/reading-list/.kube.yml | 97 + examples/servers/reading-list/Gopkg.lock | 249 + examples/servers/reading-list/Gopkg.toml | 34 + examples/servers/reading-list/README.md | 26 + .../reading-list/cloud-endpoints/create_ce.sh | 3 + .../cloud-endpoints/service-ce-prd.yaml | 13 + .../servers/reading-list/cmd/cli/README.md | 29 + examples/servers/reading-list/cmd/cli/main.go | 115 + examples/servers/reading-list/db.go | 102 + examples/servers/reading-list/gen-proto.sh | 11 + examples/servers/reading-list/get.go | 58 + examples/servers/reading-list/http_client.go | 127 + examples/servers/reading-list/put.go | 82 + .../servers/reading-list/server/Dockerfile | 7 + examples/servers/reading-list/server/main.go | 19 + .../servers/reading-list/server/run_local.sh | 8 + examples/servers/reading-list/service.go | 149 + examples/servers/reading-list/service.pb | Bin 0 -> 60807 bytes examples/servers/reading-list/service.pb.go | 292 + examples/servers/reading-list/service.proto | 49 + examples/servers/reading-list/service.yaml | 94 + .../vendor/cloud.google.com/go/.travis.yml | 16 + .../vendor/cloud.google.com/go/AUTHORS | 15 + .../cloud.google.com/go/CONTRIBUTING.md | 138 + .../vendor/cloud.google.com/go/CONTRIBUTORS | 37 + .../vendor/cloud.google.com/go/LICENSE | 202 + .../vendor/cloud.google.com/go/README.md | 442 + .../vendor/cloud.google.com/go/appveyor.yml | 32 + .../cloud.google.com/go/authexample_test.go | 49 + .../vendor/cloud.google.com/go/cloud.go | 20 + .../go/compute/metadata/metadata.go | 437 + .../go/compute/metadata/metadata_test.go | 48 + .../go/datastore/datastore.go | 604 + .../go/datastore/datastore_test.go | 3463 ++++ .../cloud.google.com/go/datastore/doc.go | 454 + .../cloud.google.com/go/datastore/errors.go | 47 + .../go/datastore/example_test.go | 545 + .../go/datastore/integration_test.go | 1040 ++ .../cloud.google.com/go/datastore/key.go | 280 + .../cloud.google.com/go/datastore/key_test.go | 210 + .../cloud.google.com/go/datastore/load.go | 491 + .../go/datastore/load_test.go | 755 + .../cloud.google.com/go/datastore/prop.go | 342 + .../cloud.google.com/go/datastore/query.go | 773 + .../go/datastore/query_test.go | 544 + .../cloud.google.com/go/datastore/save.go | 425 + .../go/datastore/save_test.go | 194 + .../cloud.google.com/go/datastore/time.go | 36 + .../go/datastore/time_test.go | 75 + .../go/datastore/transaction.go | 310 + .../go/internal/atomiccache/atomiccache.go | 58 + .../internal/atomiccache/atomiccache_test.go | 46 + .../go/internal/fields/fields.go | 444 + .../go/internal/fields/fields_test.go | 561 + .../go/internal/fields/fold.go | 156 + .../go/internal/fields/fold_test.go | 129 + .../cloud.google.com/go/internal/retry.go | 56 + .../go/internal/retry_test.go | 64 + .../go/internal/tracecontext/tracecontext.go | 83 + .../tracecontext/tracecontext_test.go | 135 + .../go/internal/version/update_version.sh | 6 + .../go/internal/version/version.go | 71 + .../go/internal/version/version_test.go | 35 + .../vendor/cloud.google.com/go/key.json.enc | Bin 0 -> 2432 bytes .../cloud.google.com/go/license_test.go | 70 + .../vendor/cloud.google.com/go/old-news.md | 435 + .../vendor/cloud.google.com/go/run-tests.sh | 86 + .../vendor/cloud.google.com/go/trace/grpc.go | 95 + .../cloud.google.com/go/trace/grpc_test.go | 178 + .../vendor/cloud.google.com/go/trace/http.go | 105 + .../cloud.google.com/go/trace/http_test.go | 151 + .../go/trace/httpexample_test.go | 57 + .../cloud.google.com/go/trace/sampling.go | 117 + .../vendor/cloud.google.com/go/trace/trace.go | 817 + .../cloud.google.com/go/trace/trace_test.go | 955 + .../github.com/NYTimes/gizmo/.gitignore | 2 + .../github.com/NYTimes/gizmo/.travis.yml | 21 + .../NYTimes/gizmo/CODE_OF_CONDUCT.md | 75 + .../github.com/NYTimes/gizmo/CONTRIBUTING.md | 35 + .../github.com/NYTimes/gizmo/LICENSE.md | 13 + .../vendor/github.com/NYTimes/gizmo/Makefile | 62 + .../vendor/github.com/NYTimes/gizmo/README.md | 252 + .../github.com/NYTimes/gizmo/config/config.go | 71 + .../github.com/NYTimes/gizmo/config/doc.go | 17 + .../github.com/NYTimes/gizmo/config/flags.go | 41 + .../NYTimes/gizmo/config/metrics/metrics.go | 111 + .../github.com/NYTimes/gizmo/coverage.sh | 45 + .../vendor/github.com/NYTimes/gizmo/doc.go | 221 + .../NYTimes/gizmo/gitcookies.sh.enc | 1 + .../NYTimes/gizmo/server/activity.go | 38 + .../NYTimes/gizmo/server/activity_test.go | 46 + .../github.com/NYTimes/gizmo/server/config.go | 170 + .../NYTimes/gizmo/server/context_gorilla.go | 48 + .../NYTimes/gizmo/server/context_native.go | 55 + .../github.com/NYTimes/gizmo/server/doc.go | 110 + .../NYTimes/gizmo/server/esxhealthcheck.go | 151 + .../gizmo/server/esxhealthcheck_test.go | 242 + .../NYTimes/gizmo/server/healthcheck.go | 84 + .../NYTimes/gizmo/server/healthcheck_test.go | 43 + .../NYTimes/gizmo/server/http_server.go | 14 + .../NYTimes/gizmo/server/http_server_go18.go | 15 + .../NYTimes/gizmo/server/kit/README.md | 11 + .../NYTimes/gizmo/server/kit/config.go | 83 + .../NYTimes/gizmo/server/kit/kitserver.go | 214 + .../gizmo/server/kit/kitserver_pb_test.go | 181 + .../gizmo/server/kit/kitserver_test.go | 152 + .../gizmo/server/kit/kitserver_test.proto | 23 + .../gizmo/server/kit/kitserver_test.yaml | 39 + .../NYTimes/gizmo/server/kit/log.go | 77 + .../NYTimes/gizmo/server/kit/response.go | 37 + .../NYTimes/gizmo/server/kit/router.go | 182 + .../NYTimes/gizmo/server/kit/server.go | 29 + .../NYTimes/gizmo/server/kit/server_gae.go | 11 + .../NYTimes/gizmo/server/kit/service.go | 93 + .../NYTimes/gizmo/server/metrics.go | 157 + .../NYTimes/gizmo/server/metrics_test.go | 187 + .../NYTimes/gizmo/server/middleware.go | 125 + .../NYTimes/gizmo/server/middleware_go17.go | 74 + .../gizmo/server/middleware_pre_go17.go | 75 + .../NYTimes/gizmo/server/middleware_test.go | 179 + .../github.com/NYTimes/gizmo/server/router.go | 110 + .../NYTimes/gizmo/server/router_test.go | 51 + .../NYTimes/gizmo/server/rpc_server.go | 321 + .../github.com/NYTimes/gizmo/server/server.go | 289 + .../NYTimes/gizmo/server/service.go | 90 + .../NYTimes/gizmo/server/service_go17.go | 24 + .../NYTimes/gizmo/server/service_pre_go17.go | 25 + .../NYTimes/gizmo/server/simple_server.go | 375 + .../gizmo/server/simple_server_go17_test.go | 47 + .../server/simple_server_pre_go17_test.go | 47 + .../gizmo/server/simple_server_test.go | 385 + .../github.com/NYTimes/gizmo/server/tcp.go | 38 + .../github.com/NYTimes/gizmo/web/doc.go | 4 + .../github.com/NYTimes/gizmo/web/func.go | 103 + .../github.com/NYTimes/gizmo/web/func_test.go | 403 + .../NYTimes/gizmo/web/vars_gorilla.go | 34 + .../NYTimes/gizmo/web/vars_native.go | 40 + .../github.com/NYTimes/logrotate/.travis.yml | 5 + .../github.com/NYTimes/logrotate/README.md | 18 + .../github.com/NYTimes/logrotate/logrotate.go | 74 + .../NYTimes/logrotate/logrotate_test.go | 47 + .../VividCortex/gohistogram/.gitignore | 2 + .../VividCortex/gohistogram/LICENSE | 19 + .../VividCortex/gohistogram/README.md | 80 + .../VividCortex/gohistogram/histogram.go | 23 + .../VividCortex/gohistogram/histogram_test.go | 62 + .../gohistogram/numerichistogram.go | 160 + .../gohistogram/sample_data_test.go | 15006 ++++++++++++++++ .../gohistogram/weightedhistogram.go | 190 + .../vendor/github.com/beorn7/perks/.gitignore | 2 + .../vendor/github.com/beorn7/perks/LICENSE | 20 + .../vendor/github.com/beorn7/perks/README.md | 31 + .../beorn7/perks/quantile/bench_test.go | 63 + .../beorn7/perks/quantile/example_test.go | 121 + .../beorn7/perks/quantile/exampledata.txt | 2388 +++ .../beorn7/perks/quantile/stream.go | 292 + .../beorn7/perks/quantile/stream_test.go | 215 + .../vendor/github.com/go-kit/kit/.gitignore | 45 + .../vendor/github.com/go-kit/kit/.travis.yml | 8 + .../github.com/go-kit/kit/CONTRIBUTING.md | 18 + .../vendor/github.com/go-kit/kit/LICENSE | 22 + .../vendor/github.com/go-kit/kit/README.md | 120 + .../vendor/github.com/go-kit/kit/ROADMAP.md | 17 + .../vendor/github.com/go-kit/kit/circle.yml | 27 + .../github.com/go-kit/kit/coverage.bash | 47 + .../go-kit/kit/docker-compose-integration.yml | 22 + .../github.com/go-kit/kit/endpoint/doc.go | 5 + .../go-kit/kit/endpoint/endpoint.go | 28 + .../kit/endpoint/endpoint_example_test.go | 49 + .../vendor/github.com/go-kit/kit/lint | 26 + .../github.com/go-kit/kit/log/README.md | 147 + .../go-kit/kit/log/benchmark_test.go | 21 + .../go-kit/kit/log/concurrency_test.go | 40 + .../vendor/github.com/go-kit/kit/log/doc.go | 116 + .../github.com/go-kit/kit/log/example_test.go | 137 + .../github.com/go-kit/kit/log/json_logger.go | 92 + .../go-kit/kit/log/json_logger_test.go | 158 + .../vendor/github.com/go-kit/kit/log/log.go | 135 + .../github.com/go-kit/kit/log/log_test.go | 191 + .../go-kit/kit/log/logfmt_logger.go | 62 + .../go-kit/kit/log/logfmt_logger_test.go | 57 + .../github.com/go-kit/kit/log/nop_logger.go | 8 + .../go-kit/kit/log/nop_logger_test.go | 26 + .../github.com/go-kit/kit/log/stdlib.go | 116 + .../github.com/go-kit/kit/log/stdlib_test.go | 205 + .../vendor/github.com/go-kit/kit/log/sync.go | 116 + .../github.com/go-kit/kit/log/sync_test.go | 83 + .../vendor/github.com/go-kit/kit/log/value.go | 102 + .../github.com/go-kit/kit/log/value_test.go | 150 + .../github.com/go-kit/kit/metrics/README.md | 97 + .../go-kit/kit/metrics/discard/discard.go | 40 + .../github.com/go-kit/kit/metrics/doc.go | 97 + .../go-kit/kit/metrics/dogstatsd/dogstatsd.go | 314 + .../kit/metrics/dogstatsd/dogstatsd_test.go | 90 + .../go-kit/kit/metrics/expvar/expvar.go | 94 + .../go-kit/kit/metrics/expvar/expvar_test.go | 38 + .../go-kit/kit/metrics/generic/generic.go | 247 + .../kit/metrics/generic/generic_test.go | 109 + .../go-kit/kit/metrics/graphite/graphite.go | 203 + .../kit/metrics/graphite/graphite_test.go | 63 + .../go-kit/kit/metrics/influx/example_test.go | 108 + .../go-kit/kit/metrics/influx/influx.go | 267 + .../go-kit/kit/metrics/influx/influx_test.go | 125 + .../kit/metrics/internal/lv/labelvalues.go | 14 + .../metrics/internal/lv/labelvalues_test.go | 22 + .../go-kit/kit/metrics/internal/lv/space.go | 145 + .../kit/metrics/internal/lv/space_test.go | 86 + .../kit/metrics/internal/ratemap/ratemap.go | 40 + .../github.com/go-kit/kit/metrics/metrics.go | 25 + .../kit/metrics/prometheus/prometheus.go | 165 + .../kit/metrics/prometheus/prometheus_test.go | 214 + .../go-kit/kit/metrics/provider/discard.go | 24 + .../go-kit/kit/metrics/provider/dogstatsd.go | 43 + .../go-kit/kit/metrics/provider/expvar.go | 31 + .../go-kit/kit/metrics/provider/graphite.go | 41 + .../go-kit/kit/metrics/provider/influx.go | 40 + .../go-kit/kit/metrics/provider/prometheus.go | 63 + .../go-kit/kit/metrics/provider/provider.go | 42 + .../go-kit/kit/metrics/provider/statsd.go | 43 + .../go-kit/kit/metrics/statsd/statsd.go | 239 + .../go-kit/kit/metrics/statsd/statsd_test.go | 66 + .../github.com/go-kit/kit/metrics/timer.go | 28 + .../go-kit/kit/metrics/timer_test.go | 33 + .../go-kit/kit/sd/benchmark_test.go | 29 + .../vendor/github.com/go-kit/kit/sd/doc.go | 6 + .../go-kit/kit/sd/endpoint_cache.go | 143 + .../go-kit/kit/sd/endpoint_cache_test.go | 180 + .../github.com/go-kit/kit/sd/endpointer.go | 90 + .../go-kit/kit/sd/endpointer_test.go | 80 + .../github.com/go-kit/kit/sd/factory.go | 17 + .../github.com/go-kit/kit/sd/instancer.go | 34 + .../github.com/go-kit/kit/sd/lb/balancer.go | 15 + .../vendor/github.com/go-kit/kit/sd/lb/doc.go | 4 + .../github.com/go-kit/kit/sd/lb/random.go | 32 + .../go-kit/kit/sd/lb/random_test.go | 52 + .../github.com/go-kit/kit/sd/lb/retry.go | 117 + .../github.com/go-kit/kit/sd/lb/retry_test.go | 141 + .../go-kit/kit/sd/lb/round_robin.go | 34 + .../go-kit/kit/sd/lb/round_robin_test.go | 95 + .../github.com/go-kit/kit/sd/registrar.go | 13 + .../github.com/go-kit/kit/transport/doc.go | 2 + .../go-kit/kit/transport/http/client.go | 179 + .../go-kit/kit/transport/http/client_test.go | 267 + .../go-kit/kit/transport/http/doc.go | 2 + .../kit/transport/http/encode_decode.go | 30 + .../go-kit/kit/transport/http/example_test.go | 36 + .../transport/http/request_response_funcs.go | 133 + .../http/request_response_funcs_test.go | 30 + .../go-kit/kit/transport/http/server.go | 221 + .../go-kit/kit/transport/http/server_test.go | 349 + .../github.com/go-kit/kit/update_deps.bash | 28 + .../github.com/go-kit/kit/util/README.md | 5 + .../github.com/go-kit/kit/util/conn/doc.go | 2 + .../go-kit/kit/util/conn/manager.go | 145 + .../go-kit/kit/util/conn/manager_test.go | 159 + .../github.com/go-logfmt/logfmt/.gitignore | 4 + .../github.com/go-logfmt/logfmt/.travis.yml | 15 + .../github.com/go-logfmt/logfmt/LICENSE | 22 + .../github.com/go-logfmt/logfmt/README.md | 33 + .../go-logfmt/logfmt/decode-bench_test.go | 75 + .../github.com/go-logfmt/logfmt/decode.go | 237 + .../go-logfmt/logfmt/decode_test.go | 184 + .../vendor/github.com/go-logfmt/logfmt/doc.go | 6 + .../github.com/go-logfmt/logfmt/encode.go | 321 + .../go-logfmt/logfmt/encode_internal_test.go | 233 + .../go-logfmt/logfmt/encode_test.go | 206 + .../go-logfmt/logfmt/example_test.go | 60 + .../github.com/go-logfmt/logfmt/fuzz.go | 126 + .../github.com/go-logfmt/logfmt/jsonstring.go | 277 + .../github.com/go-stack/stack/.travis.yml | 16 + .../github.com/go-stack/stack/LICENSE.md | 21 + .../github.com/go-stack/stack/README.md | 38 + .../github.com/go-stack/stack/format_test.go | 21 + .../vendor/github.com/go-stack/stack/stack.go | 322 + .../github.com/go-stack/stack/stack_test.go | 398 + .../go-stack/stack/stackinternal_test.go | 57 + .../github.com/golang/protobuf/.gitignore | 16 + .../github.com/golang/protobuf/.travis.yml | 17 + .../vendor/github.com/golang/protobuf/AUTHORS | 3 + .../github.com/golang/protobuf/CONTRIBUTORS | 3 + .../vendor/github.com/golang/protobuf/LICENSE | 31 + .../github.com/golang/protobuf/Make.protobuf | 40 + .../github.com/golang/protobuf/Makefile | 55 + .../github.com/golang/protobuf/README.md | 242 + .../github.com/golang/protobuf/proto/Makefile | 43 + .../golang/protobuf/proto/all_test.go | 2278 +++ .../golang/protobuf/proto/any_test.go | 300 + .../github.com/golang/protobuf/proto/clone.go | 229 + .../golang/protobuf/proto/clone_test.go | 300 + .../golang/protobuf/proto/decode.go | 970 + .../golang/protobuf/proto/decode_test.go | 258 + .../golang/protobuf/proto/encode.go | 1362 ++ .../golang/protobuf/proto/encode_test.go | 85 + .../github.com/golang/protobuf/proto/equal.go | 300 + .../golang/protobuf/proto/equal_test.go | 224 + .../golang/protobuf/proto/extensions.go | 587 + .../golang/protobuf/proto/extensions_test.go | 536 + .../github.com/golang/protobuf/proto/lib.go | 897 + .../golang/protobuf/proto/map_test.go | 46 + .../golang/protobuf/proto/message_set.go | 311 + .../golang/protobuf/proto/message_set_test.go | 66 + .../golang/protobuf/proto/pointer_reflect.go | 484 + .../golang/protobuf/proto/pointer_unsafe.go | 270 + .../golang/protobuf/proto/properties.go | 872 + .../golang/protobuf/proto/proto3_test.go | 135 + .../golang/protobuf/proto/size2_test.go | 63 + .../golang/protobuf/proto/size_test.go | 164 + .../github.com/golang/protobuf/proto/text.go | 854 + .../golang/protobuf/proto/text_parser.go | 895 + .../golang/protobuf/proto/text_parser_test.go | 673 + .../golang/protobuf/proto/text_test.go | 474 + .../golang/protobuf/protoc-gen-go/Makefile | 33 + .../protoc-gen-go/descriptor/Makefile | 36 + .../protoc-gen-go/descriptor/descriptor.pb.go | 2152 +++ .../golang/protobuf/protoc-gen-go/doc.go | 51 + .../protobuf/protoc-gen-go/link_grpc.go | 34 + .../golang/protobuf/protoc-gen-go/main.go | 98 + .../github.com/golang/protobuf/ptypes/any.go | 139 + .../golang/protobuf/ptypes/any/any.pb.go | 168 + .../golang/protobuf/ptypes/any/any.proto | 139 + .../golang/protobuf/ptypes/any_test.go | 113 + .../github.com/golang/protobuf/ptypes/doc.go | 35 + .../golang/protobuf/ptypes/duration.go | 102 + .../golang/protobuf/ptypes/duration_test.go | 121 + .../golang/protobuf/ptypes/regen.sh | 66 + .../protobuf/ptypes/struct/struct.pb.go | 382 + .../protobuf/ptypes/struct/struct.proto | 96 + .../golang/protobuf/ptypes/timestamp.go | 134 + .../protobuf/ptypes/timestamp/timestamp.pb.go | 162 + .../protobuf/ptypes/timestamp/timestamp.proto | 133 + .../golang/protobuf/ptypes/timestamp_test.go | 153 + .../protobuf/ptypes/wrappers/wrappers.pb.go | 262 + .../protobuf/ptypes/wrappers/wrappers.proto | 118 + .../github.com/gorilla/context/.travis.yml | 19 + .../vendor/github.com/gorilla/context/LICENSE | 27 + .../github.com/gorilla/context/README.md | 7 + .../github.com/gorilla/context/context.go | 143 + .../gorilla/context/context_test.go | 161 + .../vendor/github.com/gorilla/context/doc.go | 82 + .../github.com/gorilla/handlers/.travis.yml | 18 + .../github.com/gorilla/handlers/LICENSE | 22 + .../github.com/gorilla/handlers/README.md | 55 + .../github.com/gorilla/handlers/canonical.go | 74 + .../gorilla/handlers/canonical_test.go | 127 + .../github.com/gorilla/handlers/compress.go | 148 + .../gorilla/handlers/compress_test.go | 154 + .../github.com/gorilla/handlers/cors.go | 317 + .../github.com/gorilla/handlers/cors_test.go | 336 + .../vendor/github.com/gorilla/handlers/doc.go | 9 + .../github.com/gorilla/handlers/handlers.go | 399 + .../gorilla/handlers/handlers_go18.go | 21 + .../gorilla/handlers/handlers_go18_test.go | 34 + .../gorilla/handlers/handlers_pre18.go | 7 + .../gorilla/handlers/handlers_test.go | 378 + .../gorilla/handlers/proxy_headers.go | 120 + .../gorilla/handlers/proxy_headers_test.go | 111 + .../github.com/gorilla/handlers/recovery.go | 91 + .../gorilla/handlers/recovery_test.go | 44 + .../vendor/github.com/gorilla/mux/.travis.yml | 22 + .../vendor/github.com/gorilla/mux/LICENSE | 27 + .../vendor/github.com/gorilla/mux/README.md | 351 + .../github.com/gorilla/mux/bench_test.go | 49 + .../github.com/gorilla/mux/context_gorilla.go | 26 + .../gorilla/mux/context_gorilla_test.go | 40 + .../github.com/gorilla/mux/context_native.go | 24 + .../gorilla/mux/context_native_test.go | 32 + .../vendor/github.com/gorilla/mux/doc.go | 240 + .../vendor/github.com/gorilla/mux/mux.go | 542 + .../vendor/github.com/gorilla/mux/mux_test.go | 1771 ++ .../vendor/github.com/gorilla/mux/old_test.go | 710 + .../vendor/github.com/gorilla/mux/regexp.go | 323 + .../vendor/github.com/gorilla/mux/route.go | 677 + .../go-grpc-middleware/.gitignore | 199 + .../go-grpc-middleware/.travis.yml | 26 + .../grpc-ecosystem/go-grpc-middleware/DOC.md | 164 + .../grpc-ecosystem/go-grpc-middleware/LICENSE | 201 + .../go-grpc-middleware/README.md | 86 + .../go-grpc-middleware/chain.go | 143 + .../go-grpc-middleware/chain_test.go | 172 + .../go-grpc-middleware/checkup.sh | 21 + .../grpc-ecosystem/go-grpc-middleware/doc.go | 67 + .../go-grpc-middleware/fixup.sh | 36 + .../go-grpc-middleware/test_all.sh | 14 + .../go-grpc-middleware/wrappers.go | 29 + .../go-grpc-middleware/wrappers_test.go | 52 + .../github.com/hashicorp/consul/.gitignore | 21 + .../github.com/hashicorp/consul/.travis.yml | 13 + .../github.com/hashicorp/consul/CHANGELOG.md | 958 + .../github.com/hashicorp/consul/GNUmakefile | 94 + .../hashicorp/consul/ISSUE_TEMPLATE.md | 35 + .../github.com/hashicorp/consul/LICENSE | 354 + .../github.com/hashicorp/consul/README.md | 70 + .../github.com/hashicorp/consul/api/README.md | 43 + .../github.com/hashicorp/consul/api/acl.go | 175 + .../hashicorp/consul/api/acl_test.go | 157 + .../github.com/hashicorp/consul/api/agent.go | 475 + .../hashicorp/consul/api/agent_test.go | 766 + .../github.com/hashicorp/consul/api/api.go | 726 + .../hashicorp/consul/api/api_test.go | 537 + .../hashicorp/consul/api/catalog.go | 198 + .../hashicorp/consul/api/catalog_test.go | 474 + .../hashicorp/consul/api/coordinate.go | 67 + .../hashicorp/consul/api/coordinate_test.go | 44 + .../github.com/hashicorp/consul/api/event.go | 104 + .../hashicorp/consul/api/event_test.go | 50 + .../github.com/hashicorp/consul/api/health.go | 200 + .../hashicorp/consul/api/health_test.go | 354 + .../github.com/hashicorp/consul/api/kv.go | 420 + .../hashicorp/consul/api/kv_test.go | 574 + .../github.com/hashicorp/consul/api/lock.go | 385 + .../hashicorp/consul/api/lock_test.go | 560 + .../hashicorp/consul/api/operator.go | 11 + .../hashicorp/consul/api/operator_area.go | 168 + .../consul/api/operator_autopilot.go | 215 + .../consul/api/operator_autopilot_test.go | 104 + .../hashicorp/consul/api/operator_keyring.go | 83 + .../consul/api/operator_keyring_test.go | 73 + .../hashicorp/consul/api/operator_raft.go | 86 + .../consul/api/operator_raft_test.go | 38 + .../hashicorp/consul/api/prepared_query.go | 198 + .../consul/api/prepared_query_test.go | 133 + .../github.com/hashicorp/consul/api/raw.go | 24 + .../hashicorp/consul/api/semaphore.go | 513 + .../hashicorp/consul/api/semaphore_test.go | 518 + .../hashicorp/consul/api/session.go | 217 + .../hashicorp/consul/api/session_test.go | 314 + .../hashicorp/consul/api/snapshot.go | 47 + .../hashicorp/consul/api/snapshot_test.go | 134 + .../github.com/hashicorp/consul/api/status.go | 43 + .../hashicorp/consul/api/status_test.go | 37 + .../github.com/hashicorp/consul/main.go | 57 + .../github.com/hashicorp/consul/main_test.go | 1 + .../github.com/hashicorp/go-cleanhttp/LICENSE | 363 + .../hashicorp/go-cleanhttp/README.md | 30 + .../hashicorp/go-cleanhttp/cleanhttp.go | 56 + .../github.com/hashicorp/go-cleanhttp/doc.go | 20 + .../hashicorp/go-rootcerts/.travis.yml | 12 + .../github.com/hashicorp/go-rootcerts/LICENSE | 363 + .../hashicorp/go-rootcerts/Makefile | 8 + .../hashicorp/go-rootcerts/README.md | 43 + .../github.com/hashicorp/go-rootcerts/doc.go | 9 + .../hashicorp/go-rootcerts/rootcerts.go | 103 + .../hashicorp/go-rootcerts/rootcerts_base.go | 12 + .../go-rootcerts/rootcerts_darwin.go | 48 + .../go-rootcerts/rootcerts_darwin_test.go | 17 + .../hashicorp/go-rootcerts/rootcerts_test.go | 52 + .../github.com/hashicorp/serf/.gitignore | 32 + .../github.com/hashicorp/serf/.travis.yml | 15 + .../github.com/hashicorp/serf/CHANGELOG.md | 398 + .../github.com/hashicorp/serf/GNUmakefile | 64 + .../vendor/github.com/hashicorp/serf/LICENSE | 354 + .../github.com/hashicorp/serf/README.md | 121 + .../hashicorp/serf/coordinate/client.go | 180 + .../hashicorp/serf/coordinate/client_test.go | 109 + .../hashicorp/serf/coordinate/config.go | 70 + .../hashicorp/serf/coordinate/coordinate.go | 183 + .../serf/coordinate/coordinate_test.go | 260 + .../serf/coordinate/performance_test.go | 182 + .../hashicorp/serf/coordinate/phantom.go | 187 + .../hashicorp/serf/coordinate/util_test.go | 27 + .../influxdata/influxdb/.dockerignore | 1 + .../github.com/influxdata/influxdb/.gitignore | 76 + .../influxdata/influxdb/.mention-bot | 6 + .../influxdata/influxdb/CHANGELOG.md | 2665 +++ .../influxdata/influxdb/CODING_GUIDELINES.md | 82 + .../influxdata/influxdb/CONTRIBUTING.md | 280 + .../influxdb/Dockerfile_build_ubuntu32 | 35 + .../influxdb/Dockerfile_build_ubuntu64 | 38 + .../influxdb/Dockerfile_build_ubuntu64_git | 43 + .../influxdb/Dockerfile_test_ubuntu32 | 12 + .../github.com/influxdata/influxdb/Godeps | 20 + .../github.com/influxdata/influxdb/LICENSE | 20 + .../influxdb/LICENSE_OF_DEPENDENCIES.md | 26 + .../github.com/influxdata/influxdb/Makefile | 39 + .../github.com/influxdata/influxdb/QUERIES.md | 180 + .../github.com/influxdata/influxdb/README.md | 71 + .../github.com/influxdata/influxdb/TODO.md | 9 + .../influxdata/influxdb/appveyor.yml | 37 + .../github.com/influxdata/influxdb/build.py | 991 + .../github.com/influxdata/influxdb/build.sh | 22 + .../influxdata/influxdb/circle-test.sh | 39 + .../github.com/influxdata/influxdb/circle.yml | 43 + .../influxdata/influxdb/client/README.md | 298 + .../influxdb/client/example_test.go | 113 + .../influxdata/influxdb/client/influxdb.go | 832 + .../influxdb/client/influxdb_test.go | 831 + .../influxdata/influxdb/client/v2/client.go | 609 + .../influxdb/client/v2/client_test.go | 563 + .../influxdb/client/v2/example_test.go | 265 + .../influxdata/influxdb/client/v2/udp.go | 112 + .../github.com/influxdata/influxdb/errors.go | 42 + .../github.com/influxdata/influxdb/gobuild.sh | 18 + .../influxdata/influxdb/influxdb.go | 6 + .../influxdata/influxdb/models/consistency.go | 48 + .../influxdata/influxdb/models/inline_fnv.go | 32 + .../influxdb/models/inline_fnv_test.go | 29 + .../influxdb/models/inline_strconv_parse.go | 38 + .../models/inline_strconv_parse_test.go | 103 + .../influxdata/influxdb/models/points.go | 2231 +++ .../influxdb/models/points_internal_test.go | 17 + .../influxdata/influxdb/models/points_test.go | 2253 +++ .../influxdata/influxdb/models/rows.go | 62 + .../influxdata/influxdb/models/statistic.go | 42 + .../influxdb/models/statistic_test.go | 55 + .../influxdata/influxdb/models/time.go | 74 + .../github.com/influxdata/influxdb/nightly.sh | 57 + .../github.com/influxdata/influxdb/node.go | 121 + .../influxdata/influxdb/pkg/README.md | 5 + .../influxdata/influxdb/pkg/escape/bytes.go | 111 + .../influxdb/pkg/escape/bytes_test.go | 68 + .../influxdata/influxdb/pkg/escape/strings.go | 21 + .../influxdb/pkg/escape/strings_test.go | 115 + .../github.com/influxdata/influxdb/test.sh | 206 + .../julienschmidt/httprouter/.travis.yml | 17 + .../julienschmidt/httprouter/LICENSE | 24 + .../julienschmidt/httprouter/README.md | 266 + .../julienschmidt/httprouter/path.go | 123 + .../julienschmidt/httprouter/path_test.go | 92 + .../julienschmidt/httprouter/router.go | 411 + .../julienschmidt/httprouter/router_test.go | 530 + .../julienschmidt/httprouter/tree.go | 656 + .../julienschmidt/httprouter/tree_test.go | 699 + .../kelseyhightower/envconfig/.travis.yml | 7 + .../kelseyhightower/envconfig/LICENSE | 19 + .../kelseyhightower/envconfig/MAINTAINERS | 2 + .../kelseyhightower/envconfig/README.md | 188 + .../kelseyhightower/envconfig/doc.go | 8 + .../kelseyhightower/envconfig/env_os.go | 7 + .../kelseyhightower/envconfig/env_syscall.go | 7 + .../kelseyhightower/envconfig/envconfig.go | 319 + .../envconfig/envconfig_test.go | 688 + .../kelseyhightower/envconfig/usage.go | 158 + .../kelseyhightower/envconfig/usage_test.go | 155 + .../vendor/github.com/kr/logfmt/.gitignore | 3 + .../vendor/github.com/kr/logfmt/Readme | 12 + .../vendor/github.com/kr/logfmt/bench_test.go | 59 + .../vendor/github.com/kr/logfmt/decode.go | 184 + .../github.com/kr/logfmt/decode_test.go | 110 + .../github.com/kr/logfmt/example_test.go | 59 + .../vendor/github.com/kr/logfmt/scanner.go | 149 + .../github.com/kr/logfmt/scanner_test.go | 67 + .../vendor/github.com/kr/logfmt/unquote.go | 149 + .../golang_protobuf_extensions/.travis.yml | 2 + .../golang_protobuf_extensions/LICENSE | 201 + .../golang_protobuf_extensions/NOTICE | 1 + .../golang_protobuf_extensions/README.md | 20 + .../pbutil/all_test.go | 177 + .../pbutil/decode.go | 75 + .../pbutil/decode_test.go | 99 + .../golang_protobuf_extensions/pbutil/doc.go | 16 + .../pbutil/encode.go | 46 + .../pbutil/encode_test.go | 67 + .../pbutil/fixtures_test.go | 103 + .../github.com/mitchellh/go-homedir/LICENSE | 21 + .../github.com/mitchellh/go-homedir/README.md | 14 + .../mitchellh/go-homedir/homedir.go | 137 + .../mitchellh/go-homedir/homedir_test.go | 112 + .../github.com/nu7hatch/gouuid/.gitignore | 11 + .../vendor/github.com/nu7hatch/gouuid/COPYING | 19 + .../github.com/nu7hatch/gouuid/README.md | 21 + .../nu7hatch/gouuid/example_test.go | 33 + .../vendor/github.com/nu7hatch/gouuid/uuid.go | 173 + .../github.com/nu7hatch/gouuid/uuid_test.go | 135 + .../vendor/github.com/pkg/errors/.gitignore | 24 + .../vendor/github.com/pkg/errors/.travis.yml | 11 + .../vendor/github.com/pkg/errors/LICENSE | 23 + .../vendor/github.com/pkg/errors/README.md | 52 + .../vendor/github.com/pkg/errors/appveyor.yml | 32 + .../github.com/pkg/errors/bench_test.go | 59 + .../vendor/github.com/pkg/errors/errors.go | 269 + .../github.com/pkg/errors/errors_test.go | 226 + .../github.com/pkg/errors/example_test.go | 205 + .../github.com/pkg/errors/format_test.go | 535 + .../vendor/github.com/pkg/errors/stack.go | 178 + .../github.com/pkg/errors/stack_test.go | 292 + .../prometheus/client_golang/.gitignore | 26 + .../prometheus/client_golang/.travis.yml | 9 + .../prometheus/client_golang/AUTHORS.md | 18 + .../prometheus/client_golang/CHANGELOG.md | 109 + .../prometheus/client_golang/CONTRIBUTING.md | 18 + .../prometheus/client_golang/LICENSE | 201 + .../prometheus/client_golang/NOTICE | 23 + .../prometheus/client_golang/README.md | 45 + .../prometheus/client_golang/VERSION | 1 + .../client_golang/prometheus/.gitignore | 1 + .../client_golang/prometheus/README.md | 1 + .../prometheus/benchmark_test.go | 183 + .../client_golang/prometheus/collector.go | 75 + .../client_golang/prometheus/counter.go | 172 + .../client_golang/prometheus/counter_test.go | 58 + .../client_golang/prometheus/desc.go | 205 + .../client_golang/prometheus/doc.go | 181 + .../prometheus/example_clustermanager_test.go | 118 + .../client_golang/prometheus/examples_test.go | 751 + .../prometheus/expvar_collector.go | 119 + .../prometheus/expvar_collector_test.go | 97 + .../client_golang/prometheus/fnv.go | 29 + .../client_golang/prometheus/gauge.go | 140 + .../client_golang/prometheus/gauge_test.go | 182 + .../client_golang/prometheus/go_collector.go | 263 + .../prometheus/go_collector_test.go | 123 + .../client_golang/prometheus/histogram.go | 444 + .../prometheus/histogram_test.go | 326 + .../client_golang/prometheus/http.go | 490 + .../client_golang/prometheus/http_test.go | 121 + .../client_golang/prometheus/metric.go | 166 + .../client_golang/prometheus/metric_test.go | 35 + .../prometheus/process_collector.go | 142 + .../prometheus/process_collector_test.go | 58 + .../client_golang/prometheus/registry.go | 806 + .../client_golang/prometheus/registry_test.go | 545 + .../client_golang/prometheus/summary.go | 534 + .../client_golang/prometheus/summary_test.go | 347 + .../client_golang/prometheus/untyped.go | 138 + .../client_golang/prometheus/value.go | 234 + .../client_golang/prometheus/vec.go | 404 + .../client_golang/prometheus/vec_test.go | 312 + .../prometheus/client_model/.gitignore | 1 + .../prometheus/client_model/CONTRIBUTING.md | 18 + .../prometheus/client_model/LICENSE | 201 + .../prometheus/client_model/MAINTAINERS.md | 1 + .../prometheus/client_model/Makefile | 61 + .../github.com/prometheus/client_model/NOTICE | 5 + .../prometheus/client_model/README.md | 26 + .../prometheus/client_model/go/metrics.pb.go | 364 + .../prometheus/client_model/metrics.proto | 81 + .../prometheus/client_model/pom.xml | 130 + .../prometheus/client_model/setup.py | 23 + .../github.com/prometheus/common/.travis.yml | 6 + .../prometheus/common/CONTRIBUTING.md | 18 + .../github.com/prometheus/common/LICENSE | 201 + .../prometheus/common/MAINTAINERS.md | 1 + .../github.com/prometheus/common/NOTICE | 5 + .../github.com/prometheus/common/README.md | 12 + .../prometheus/common/expfmt/bench_test.go | 167 + .../prometheus/common/expfmt/decode.go | 429 + .../prometheus/common/expfmt/decode_test.go | 435 + .../prometheus/common/expfmt/encode.go | 88 + .../prometheus/common/expfmt/expfmt.go | 38 + .../prometheus/common/expfmt/fuzz.go | 36 + .../prometheus/common/expfmt/text_create.go | 303 + .../common/expfmt/text_create_test.go | 443 + .../prometheus/common/expfmt/text_parse.go | 757 + .../common/expfmt/text_parse_test.go | 593 + .../bitbucket.org/ww/goautoneg/README.txt | 67 + .../bitbucket.org/ww/goautoneg/autoneg.go | 162 + .../ww/goautoneg/autoneg_test.go | 33 + .../prometheus/common/model/alert.go | 136 + .../prometheus/common/model/alert_test.go | 118 + .../prometheus/common/model/fingerprinting.go | 105 + .../github.com/prometheus/common/model/fnv.go | 42 + .../prometheus/common/model/labels.go | 210 + .../prometheus/common/model/labels_test.go | 140 + .../prometheus/common/model/labelset.go | 169 + .../prometheus/common/model/metric.go | 103 + .../prometheus/common/model/metric_test.go | 132 + .../prometheus/common/model/model.go | 16 + .../prometheus/common/model/signature.go | 144 + .../prometheus/common/model/signature_test.go | 314 + .../prometheus/common/model/silence.go | 106 + .../prometheus/common/model/silence_test.go | 228 + .../prometheus/common/model/time.go | 261 + .../prometheus/common/model/time_test.go | 129 + .../prometheus/common/model/value.go | 416 + .../prometheus/common/model/value_test.go | 468 + .../github.com/prometheus/procfs/.travis.yml | 5 + .../prometheus/procfs/CONTRIBUTING.md | 18 + .../github.com/prometheus/procfs/LICENSE | 201 + .../prometheus/procfs/MAINTAINERS.md | 1 + .../github.com/prometheus/procfs/Makefile | 18 + .../github.com/prometheus/procfs/NOTICE | 7 + .../github.com/prometheus/procfs/README.md | 11 + .../github.com/prometheus/procfs/buddyinfo.go | 95 + .../prometheus/procfs/buddyinfo_test.go | 64 + .../github.com/prometheus/procfs/doc.go | 45 + .../vendor/github.com/prometheus/procfs/fs.go | 46 + .../github.com/prometheus/procfs/fs_test.go | 26 + .../github.com/prometheus/procfs/ipvs.go | 246 + .../github.com/prometheus/procfs/ipvs_test.go | 237 + .../github.com/prometheus/procfs/mdstat.go | 138 + .../prometheus/procfs/mdstat_test.go | 31 + .../prometheus/procfs/mountstats.go | 556 + .../prometheus/procfs/mountstats_test.go | 273 + .../github.com/prometheus/procfs/proc.go | 224 + .../github.com/prometheus/procfs/proc_io.go | 55 + .../prometheus/procfs/proc_io_test.go | 33 + .../prometheus/procfs/proc_limits.go | 137 + .../prometheus/procfs/proc_limits_test.go | 31 + .../github.com/prometheus/procfs/proc_stat.go | 175 + .../prometheus/procfs/proc_stat_test.go | 110 + .../github.com/prometheus/procfs/proc_test.go | 160 + .../github.com/prometheus/procfs/stat.go | 219 + .../github.com/prometheus/procfs/stat_test.go | 61 + .../vendor/github.com/prometheus/procfs/ttar | 264 + .../github.com/prometheus/procfs/xfrm.go | 187 + .../github.com/prometheus/procfs/xfrm_test.go | 66 + .../github.com/prometheus/procfs/xfs/parse.go | 359 + .../prometheus/procfs/xfs/parse_test.go | 442 + .../github.com/prometheus/procfs/xfs/xfs.go | 163 + .../github.com/sirupsen/logrus/.gitignore | 1 + .../github.com/sirupsen/logrus/.travis.yml | 12 + .../github.com/sirupsen/logrus/CHANGELOG.md | 109 + .../vendor/github.com/sirupsen/logrus/LICENSE | 21 + .../github.com/sirupsen/logrus/README.md | 504 + .../github.com/sirupsen/logrus/alt_exit.go | 64 + .../sirupsen/logrus/alt_exit_test.go | 74 + .../vendor/github.com/sirupsen/logrus/doc.go | 26 + .../github.com/sirupsen/logrus/entry.go | 275 + .../github.com/sirupsen/logrus/entry_test.go | 77 + .../github.com/sirupsen/logrus/exported.go | 193 + .../github.com/sirupsen/logrus/formatter.go | 45 + .../sirupsen/logrus/formatter_bench_test.go | 101 + .../github.com/sirupsen/logrus/hook_test.go | 122 + .../github.com/sirupsen/logrus/hooks.go | 34 + .../sirupsen/logrus/json_formatter.go | 74 + .../sirupsen/logrus/json_formatter_test.go | 199 + .../github.com/sirupsen/logrus/logger.go | 317 + .../sirupsen/logrus/logger_bench_test.go | 61 + .../github.com/sirupsen/logrus/logrus.go | 143 + .../github.com/sirupsen/logrus/logrus_test.go | 386 + .../sirupsen/logrus/terminal_appengine.go | 10 + .../sirupsen/logrus/terminal_bsd.go | 10 + .../sirupsen/logrus/terminal_linux.go | 14 + .../sirupsen/logrus/terminal_notwindows.go | 28 + .../sirupsen/logrus/terminal_solaris.go | 21 + .../sirupsen/logrus/terminal_windows.go | 82 + .../sirupsen/logrus/text_formatter.go | 189 + .../sirupsen/logrus/text_formatter_test.go | 161 + .../github.com/sirupsen/logrus/writer.go | 62 + .../vendor/golang.org/x/net/.gitattributes | 10 + .../vendor/golang.org/x/net/.gitignore | 2 + .../vendor/golang.org/x/net/AUTHORS | 3 + .../vendor/golang.org/x/net/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/net/CONTRIBUTORS | 3 + .../vendor/golang.org/x/net/LICENSE | 27 + .../vendor/golang.org/x/net/PATENTS | 22 + .../vendor/golang.org/x/net/README | 3 + .../vendor/golang.org/x/net/codereview.cfg | 1 + .../golang.org/x/net/context/context.go | 54 + .../golang.org/x/net/context/context_test.go | 583 + .../x/net/context/ctxhttp/ctxhttp.go | 74 + .../x/net/context/ctxhttp/ctxhttp_17_test.go | 29 + .../x/net/context/ctxhttp/ctxhttp_pre17.go | 147 + .../net/context/ctxhttp/ctxhttp_pre17_test.go | 79 + .../x/net/context/ctxhttp/ctxhttp_test.go | 105 + .../vendor/golang.org/x/net/context/go17.go | 72 + .../vendor/golang.org/x/net/context/go19.go | 20 + .../golang.org/x/net/context/pre_go17.go | 300 + .../golang.org/x/net/context/pre_go19.go | 109 + .../x/net/context/withtimeout_test.go | 31 + .../vendor/golang.org/x/net/http2/.gitignore | 2 + .../vendor/golang.org/x/net/http2/Dockerfile | 51 + .../vendor/golang.org/x/net/http2/Makefile | 3 + .../vendor/golang.org/x/net/http2/README | 20 + .../vendor/golang.org/x/net/http2/ciphers.go | 641 + .../golang.org/x/net/http2/ciphers_test.go | 309 + .../x/net/http2/client_conn_pool.go | 256 + .../x/net/http2/configure_transport.go | 80 + .../golang.org/x/net/http2/databuffer.go | 146 + .../golang.org/x/net/http2/databuffer_test.go | 157 + .../vendor/golang.org/x/net/http2/errors.go | 133 + .../golang.org/x/net/http2/errors_test.go | 24 + .../vendor/golang.org/x/net/http2/flow.go | 50 + .../golang.org/x/net/http2/flow_test.go | 53 + .../vendor/golang.org/x/net/http2/frame.go | 1579 ++ .../golang.org/x/net/http2/frame_test.go | 1191 ++ .../vendor/golang.org/x/net/http2/go16.go | 16 + .../vendor/golang.org/x/net/http2/go17.go | 106 + .../golang.org/x/net/http2/go17_not18.go | 36 + .../vendor/golang.org/x/net/http2/go18.go | 56 + .../golang.org/x/net/http2/go18_test.go | 79 + .../vendor/golang.org/x/net/http2/go19.go | 16 + .../golang.org/x/net/http2/go19_test.go | 60 + .../vendor/golang.org/x/net/http2/gotrack.go | 170 + .../golang.org/x/net/http2/gotrack_test.go | 33 + .../golang.org/x/net/http2/headermap.go | 78 + .../golang.org/x/net/http2/hpack/encode.go | 240 + .../x/net/http2/hpack/encode_test.go | 386 + .../golang.org/x/net/http2/hpack/hpack.go | 490 + .../x/net/http2/hpack/hpack_test.go | 722 + .../golang.org/x/net/http2/hpack/huffman.go | 212 + .../golang.org/x/net/http2/hpack/tables.go | 479 + .../x/net/http2/hpack/tables_test.go | 214 + .../vendor/golang.org/x/net/http2/http2.go | 391 + .../golang.org/x/net/http2/http2_test.go | 199 + .../vendor/golang.org/x/net/http2/not_go16.go | 21 + .../vendor/golang.org/x/net/http2/not_go17.go | 87 + .../vendor/golang.org/x/net/http2/not_go18.go | 29 + .../vendor/golang.org/x/net/http2/not_go19.go | 16 + .../vendor/golang.org/x/net/http2/pipe.go | 163 + .../golang.org/x/net/http2/pipe_test.go | 130 + .../vendor/golang.org/x/net/http2/server.go | 2857 +++ .../x/net/http2/server_push_test.go | 521 + .../golang.org/x/net/http2/server_test.go | 3721 ++++ .../golang.org/x/net/http2/transport.go | 2134 +++ .../golang.org/x/net/http2/transport_test.go | 3041 ++++ .../vendor/golang.org/x/net/http2/write.go | 370 + .../golang.org/x/net/http2/writesched.go | 242 + .../x/net/http2/writesched_priority.go | 452 + .../x/net/http2/writesched_priority_test.go | 541 + .../x/net/http2/writesched_random.go | 72 + .../x/net/http2/writesched_random_test.go | 44 + .../golang.org/x/net/http2/writesched_test.go | 125 + .../golang.org/x/net/http2/z_spec_test.go | 356 + .../golang.org/x/net/idna/example_test.go | 70 + .../vendor/golang.org/x/net/idna/idna.go | 680 + .../vendor/golang.org/x/net/idna/idna_test.go | 43 + .../vendor/golang.org/x/net/idna/punycode.go | 203 + .../golang.org/x/net/idna/punycode_test.go | 198 + .../vendor/golang.org/x/net/idna/tables.go | 4477 +++++ .../vendor/golang.org/x/net/idna/trie.go | 72 + .../vendor/golang.org/x/net/idna/trieval.go | 114 + .../x/net/internal/timeseries/timeseries.go | 525 + .../internal/timeseries/timeseries_test.go | 170 + .../golang.org/x/net/lex/httplex/httplex.go | 351 + .../x/net/lex/httplex/httplex_test.go | 119 + .../vendor/golang.org/x/net/trace/events.go | 532 + .../golang.org/x/net/trace/histogram.go | 365 + .../golang.org/x/net/trace/histogram_test.go | 325 + .../vendor/golang.org/x/net/trace/trace.go | 1082 ++ .../golang.org/x/net/trace/trace_go16.go | 21 + .../golang.org/x/net/trace/trace_go17.go | 21 + .../golang.org/x/net/trace/trace_test.go | 178 + .../vendor/golang.org/x/oauth2/.travis.yml | 13 + .../vendor/golang.org/x/oauth2/AUTHORS | 3 + .../golang.org/x/oauth2/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/oauth2/CONTRIBUTORS | 3 + .../vendor/golang.org/x/oauth2/LICENSE | 27 + .../vendor/golang.org/x/oauth2/README.md | 74 + .../golang.org/x/oauth2/client_appengine.go | 25 + .../golang.org/x/oauth2/example_test.go | 71 + .../golang.org/x/oauth2/google/appengine.go | 89 + .../x/oauth2/google/appengine_hook.go | 14 + .../x/oauth2/google/appengineflex_hook.go | 11 + .../golang.org/x/oauth2/google/default.go | 130 + .../x/oauth2/google/example_test.go | 150 + .../golang.org/x/oauth2/google/google.go | 202 + .../golang.org/x/oauth2/google/google_test.go | 116 + .../vendor/golang.org/x/oauth2/google/jwt.go | 74 + .../golang.org/x/oauth2/google/jwt_test.go | 91 + .../vendor/golang.org/x/oauth2/google/sdk.go | 172 + .../golang.org/x/oauth2/google/sdk_test.go | 46 + .../golang.org/x/oauth2/internal/oauth2.go | 76 + .../x/oauth2/internal/oauth2_test.go | 62 + .../golang.org/x/oauth2/internal/token.go | 251 + .../x/oauth2/internal/token_test.go | 105 + .../golang.org/x/oauth2/internal/transport.go | 69 + .../x/oauth2/internal/transport_test.go | 38 + .../vendor/golang.org/x/oauth2/jws/jws.go | 182 + .../golang.org/x/oauth2/jws/jws_test.go | 46 + .../golang.org/x/oauth2/jwt/example_test.go | 33 + .../vendor/golang.org/x/oauth2/jwt/jwt.go | 159 + .../golang.org/x/oauth2/jwt/jwt_test.go | 190 + .../vendor/golang.org/x/oauth2/oauth2.go | 340 + .../vendor/golang.org/x/oauth2/oauth2_test.go | 471 + .../vendor/golang.org/x/oauth2/token.go | 158 + .../vendor/golang.org/x/oauth2/token_test.go | 72 + .../vendor/golang.org/x/oauth2/transport.go | 132 + .../golang.org/x/oauth2/transport_test.go | 108 + .../vendor/golang.org/x/sync/AUTHORS | 3 + .../vendor/golang.org/x/sync/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/sync/CONTRIBUTORS | 3 + .../vendor/golang.org/x/sync/LICENSE | 27 + .../vendor/golang.org/x/sync/PATENTS | 22 + .../vendor/golang.org/x/sync/README | 2 + .../vendor/golang.org/x/sync/codereview.cfg | 1 + .../golang.org/x/sync/semaphore/semaphore.go | 131 + .../x/sync/semaphore/semaphore_bench_test.go | 130 + .../x/sync/semaphore/semaphore_test.go | 170 + .../vendor/golang.org/x/sys/.gitattributes | 10 + .../vendor/golang.org/x/sys/.gitignore | 2 + .../vendor/golang.org/x/sys/AUTHORS | 3 + .../vendor/golang.org/x/sys/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/sys/CONTRIBUTORS | 3 + .../vendor/golang.org/x/sys/LICENSE | 27 + .../vendor/golang.org/x/sys/PATENTS | 22 + .../vendor/golang.org/x/sys/README | 3 + .../vendor/golang.org/x/sys/codereview.cfg | 1 + .../vendor/golang.org/x/sys/unix/.gitignore | 1 + .../vendor/golang.org/x/sys/unix/README.md | 173 + .../golang.org/x/sys/unix/asm_darwin_386.s | 29 + .../golang.org/x/sys/unix/asm_darwin_amd64.s | 29 + .../golang.org/x/sys/unix/asm_darwin_arm.s | 30 + .../golang.org/x/sys/unix/asm_darwin_arm64.s | 30 + .../x/sys/unix/asm_dragonfly_amd64.s | 29 + .../golang.org/x/sys/unix/asm_freebsd_386.s | 29 + .../golang.org/x/sys/unix/asm_freebsd_amd64.s | 29 + .../golang.org/x/sys/unix/asm_freebsd_arm.s | 29 + .../golang.org/x/sys/unix/asm_linux_386.s | 35 + .../golang.org/x/sys/unix/asm_linux_amd64.s | 29 + .../golang.org/x/sys/unix/asm_linux_arm.s | 29 + .../golang.org/x/sys/unix/asm_linux_arm64.s | 24 + .../golang.org/x/sys/unix/asm_linux_mips64x.s | 28 + .../golang.org/x/sys/unix/asm_linux_mipsx.s | 31 + .../golang.org/x/sys/unix/asm_linux_ppc64x.s | 28 + .../golang.org/x/sys/unix/asm_linux_s390x.s | 28 + .../golang.org/x/sys/unix/asm_netbsd_386.s | 29 + .../golang.org/x/sys/unix/asm_netbsd_amd64.s | 29 + .../golang.org/x/sys/unix/asm_netbsd_arm.s | 29 + .../golang.org/x/sys/unix/asm_openbsd_386.s | 29 + .../golang.org/x/sys/unix/asm_openbsd_amd64.s | 29 + .../golang.org/x/sys/unix/asm_solaris_amd64.s | 17 + .../golang.org/x/sys/unix/bluetooth_linux.go | 35 + .../vendor/golang.org/x/sys/unix/constants.go | 13 + .../golang.org/x/sys/unix/creds_test.go | 121 + .../vendor/golang.org/x/sys/unix/dirent.go | 102 + .../golang.org/x/sys/unix/endian_big.go | 9 + .../golang.org/x/sys/unix/endian_little.go | 9 + .../vendor/golang.org/x/sys/unix/env_unix.go | 27 + .../vendor/golang.org/x/sys/unix/env_unset.go | 14 + .../golang.org/x/sys/unix/export_test.go | 9 + .../vendor/golang.org/x/sys/unix/flock.go | 24 + .../x/sys/unix/flock_linux_32bit.go | 13 + .../vendor/golang.org/x/sys/unix/gccgo.go | 46 + .../vendor/golang.org/x/sys/unix/gccgo_c.c | 41 + .../x/sys/unix/gccgo_linux_amd64.go | 20 + .../x/sys/unix/gccgo_linux_sparc64.go | 20 + .../vendor/golang.org/x/sys/unix/mkall.sh | 179 + .../vendor/golang.org/x/sys/unix/mkerrors.sh | 556 + .../vendor/golang.org/x/sys/unix/mkpost.go | 88 + .../vendor/golang.org/x/sys/unix/mksyscall.pl | 328 + .../x/sys/unix/mksyscall_solaris.pl | 289 + .../golang.org/x/sys/unix/mksysctl_openbsd.pl | 264 + .../golang.org/x/sys/unix/mksysnum_darwin.pl | 39 + .../x/sys/unix/mksysnum_dragonfly.pl | 50 + .../golang.org/x/sys/unix/mksysnum_freebsd.pl | 63 + .../golang.org/x/sys/unix/mksysnum_netbsd.pl | 58 + .../golang.org/x/sys/unix/mksysnum_openbsd.pl | 50 + .../golang.org/x/sys/unix/mmap_unix_test.go | 23 + .../golang.org/x/sys/unix/openbsd_pledge.go | 38 + .../golang.org/x/sys/unix/openbsd_test.go | 113 + .../vendor/golang.org/x/sys/unix/race.go | 30 + .../vendor/golang.org/x/sys/unix/race0.go | 25 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 36 + .../golang.org/x/sys/unix/sockcmsg_unix.go | 104 + .../vendor/golang.org/x/sys/unix/str.go | 26 + .../vendor/golang.org/x/sys/unix/syscall.go | 69 + .../golang.org/x/sys/unix/syscall_bsd.go | 614 + .../golang.org/x/sys/unix/syscall_bsd_test.go | 62 + .../golang.org/x/sys/unix/syscall_darwin.go | 493 + .../x/sys/unix/syscall_darwin_386.go | 77 + .../x/sys/unix/syscall_darwin_amd64.go | 79 + .../x/sys/unix/syscall_darwin_arm.go | 71 + .../x/sys/unix/syscall_darwin_arm64.go | 77 + .../x/sys/unix/syscall_dragonfly.go | 425 + .../x/sys/unix/syscall_dragonfly_amd64.go | 61 + .../golang.org/x/sys/unix/syscall_freebsd.go | 666 + .../x/sys/unix/syscall_freebsd_386.go | 61 + .../x/sys/unix/syscall_freebsd_amd64.go | 61 + .../x/sys/unix/syscall_freebsd_arm.go | 61 + .../x/sys/unix/syscall_freebsd_test.go | 20 + .../golang.org/x/sys/unix/syscall_linux.go | 1459 ++ .../x/sys/unix/syscall_linux_386.go | 399 + .../x/sys/unix/syscall_linux_amd64.go | 152 + .../x/sys/unix/syscall_linux_amd64_gc.go | 13 + .../x/sys/unix/syscall_linux_arm.go | 263 + .../x/sys/unix/syscall_linux_arm64.go | 190 + .../x/sys/unix/syscall_linux_mips64x.go | 209 + .../x/sys/unix/syscall_linux_mipsx.go | 239 + .../x/sys/unix/syscall_linux_ppc64x.go | 135 + .../x/sys/unix/syscall_linux_s390x.go | 328 + .../x/sys/unix/syscall_linux_sparc64.go | 169 + .../x/sys/unix/syscall_linux_test.go | 227 + .../golang.org/x/sys/unix/syscall_netbsd.go | 476 + .../x/sys/unix/syscall_netbsd_386.go | 42 + .../x/sys/unix/syscall_netbsd_amd64.go | 42 + .../x/sys/unix/syscall_netbsd_arm.go | 42 + .../golang.org/x/sys/unix/syscall_no_getwd.go | 11 + .../golang.org/x/sys/unix/syscall_openbsd.go | 287 + .../x/sys/unix/syscall_openbsd_386.go | 42 + .../x/sys/unix/syscall_openbsd_amd64.go | 42 + .../golang.org/x/sys/unix/syscall_solaris.go | 715 + .../x/sys/unix/syscall_solaris_amd64.go | 35 + .../x/sys/unix/syscall_solaris_test.go | 34 + .../golang.org/x/sys/unix/syscall_test.go | 50 + .../golang.org/x/sys/unix/syscall_unix.go | 293 + .../golang.org/x/sys/unix/syscall_unix_gc.go | 15 + .../x/sys/unix/syscall_unix_test.go | 345 + .../golang.org/x/sys/unix/types_darwin.go | 250 + .../golang.org/x/sys/unix/types_dragonfly.go | 242 + .../golang.org/x/sys/unix/types_freebsd.go | 353 + .../golang.org/x/sys/unix/types_netbsd.go | 232 + .../golang.org/x/sys/unix/types_openbsd.go | 244 + .../golang.org/x/sys/unix/types_solaris.go | 269 + .../x/sys/unix/zerrors_darwin_386.go | 1576 ++ .../x/sys/unix/zerrors_darwin_amd64.go | 1576 ++ .../x/sys/unix/zerrors_darwin_arm.go | 1293 ++ .../x/sys/unix/zerrors_darwin_arm64.go | 1576 ++ .../x/sys/unix/zerrors_dragonfly_amd64.go | 1568 ++ .../x/sys/unix/zerrors_freebsd_386.go | 1743 ++ .../x/sys/unix/zerrors_freebsd_amd64.go | 1748 ++ .../x/sys/unix/zerrors_freebsd_arm.go | 1729 ++ .../x/sys/unix/zerrors_linux_386.go | 2180 +++ .../x/sys/unix/zerrors_linux_amd64.go | 2181 +++ .../x/sys/unix/zerrors_linux_arm.go | 2185 +++ .../x/sys/unix/zerrors_linux_arm64.go | 2170 +++ .../x/sys/unix/zerrors_linux_mips.go | 2189 +++ .../x/sys/unix/zerrors_linux_mips64.go | 2189 +++ .../x/sys/unix/zerrors_linux_mips64le.go | 2189 +++ .../x/sys/unix/zerrors_linux_mipsle.go | 2189 +++ .../x/sys/unix/zerrors_linux_ppc64.go | 2243 +++ .../x/sys/unix/zerrors_linux_ppc64le.go | 2243 +++ .../x/sys/unix/zerrors_linux_s390x.go | 2242 +++ .../x/sys/unix/zerrors_linux_sparc64.go | 2142 +++ .../x/sys/unix/zerrors_netbsd_386.go | 1712 ++ .../x/sys/unix/zerrors_netbsd_amd64.go | 1702 ++ .../x/sys/unix/zerrors_netbsd_arm.go | 1688 ++ .../x/sys/unix/zerrors_openbsd_386.go | 1584 ++ .../x/sys/unix/zerrors_openbsd_amd64.go | 1583 ++ .../x/sys/unix/zerrors_solaris_amd64.go | 1483 ++ .../x/sys/unix/zsyscall_darwin_386.go | 1394 ++ .../x/sys/unix/zsyscall_darwin_amd64.go | 1409 ++ .../x/sys/unix/zsyscall_darwin_arm.go | 1394 ++ .../x/sys/unix/zsyscall_darwin_arm64.go | 1394 ++ .../x/sys/unix/zsyscall_dragonfly_amd64.go | 1393 ++ .../x/sys/unix/zsyscall_freebsd_386.go | 1617 ++ .../x/sys/unix/zsyscall_freebsd_amd64.go | 1617 ++ .../x/sys/unix/zsyscall_freebsd_arm.go | 1617 ++ .../x/sys/unix/zsyscall_linux_386.go | 1927 ++ .../x/sys/unix/zsyscall_linux_amd64.go | 2120 +++ .../x/sys/unix/zsyscall_linux_arm.go | 2029 +++ .../x/sys/unix/zsyscall_linux_arm64.go | 2003 +++ .../x/sys/unix/zsyscall_linux_mips.go | 2085 +++ .../x/sys/unix/zsyscall_linux_mips64.go | 2079 +++ .../x/sys/unix/zsyscall_linux_mips64le.go | 2079 +++ .../x/sys/unix/zsyscall_linux_mipsle.go | 2085 +++ .../x/sys/unix/zsyscall_linux_ppc64.go | 2131 +++ .../x/sys/unix/zsyscall_linux_ppc64le.go | 2131 +++ .../x/sys/unix/zsyscall_linux_s390x.go | 1911 ++ .../x/sys/unix/zsyscall_linux_sparc64.go | 1833 ++ .../x/sys/unix/zsyscall_netbsd_386.go | 1299 ++ .../x/sys/unix/zsyscall_netbsd_amd64.go | 1299 ++ .../x/sys/unix/zsyscall_netbsd_arm.go | 1299 ++ .../x/sys/unix/zsyscall_openbsd_386.go | 1357 ++ .../x/sys/unix/zsyscall_openbsd_amd64.go | 1357 ++ .../x/sys/unix/zsyscall_solaris_amd64.go | 1589 ++ .../golang.org/x/sys/unix/zsysctl_openbsd.go | 270 + .../x/sys/unix/zsysnum_darwin_386.go | 398 + .../x/sys/unix/zsysnum_darwin_amd64.go | 398 + .../x/sys/unix/zsysnum_darwin_arm.go | 358 + .../x/sys/unix/zsysnum_darwin_arm64.go | 398 + .../x/sys/unix/zsysnum_dragonfly_amd64.go | 315 + .../x/sys/unix/zsysnum_freebsd_386.go | 351 + .../x/sys/unix/zsysnum_freebsd_amd64.go | 351 + .../x/sys/unix/zsysnum_freebsd_arm.go | 351 + .../x/sys/unix/zsysnum_linux_386.go | 388 + .../x/sys/unix/zsysnum_linux_amd64.go | 341 + .../x/sys/unix/zsysnum_linux_arm.go | 361 + .../x/sys/unix/zsysnum_linux_arm64.go | 285 + .../x/sys/unix/zsysnum_linux_mips.go | 374 + .../x/sys/unix/zsysnum_linux_mips64.go | 334 + .../x/sys/unix/zsysnum_linux_mips64le.go | 334 + .../x/sys/unix/zsysnum_linux_mipsle.go | 374 + .../x/sys/unix/zsysnum_linux_ppc64.go | 369 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 369 + .../x/sys/unix/zsysnum_linux_s390x.go | 331 + .../x/sys/unix/zsysnum_linux_sparc64.go | 348 + .../x/sys/unix/zsysnum_netbsd_386.go | 273 + .../x/sys/unix/zsysnum_netbsd_amd64.go | 273 + .../x/sys/unix/zsysnum_netbsd_arm.go | 273 + .../x/sys/unix/zsysnum_openbsd_386.go | 207 + .../x/sys/unix/zsysnum_openbsd_amd64.go | 207 + .../x/sys/unix/zsysnum_solaris_amd64.go | 13 + .../x/sys/unix/ztypes_darwin_386.go | 447 + .../x/sys/unix/ztypes_darwin_amd64.go | 462 + .../x/sys/unix/ztypes_darwin_arm.go | 449 + .../x/sys/unix/ztypes_darwin_arm64.go | 457 + .../x/sys/unix/ztypes_dragonfly_amd64.go | 443 + .../x/sys/unix/ztypes_freebsd_386.go | 502 + .../x/sys/unix/ztypes_freebsd_amd64.go | 505 + .../x/sys/unix/ztypes_freebsd_arm.go | 497 + .../golang.org/x/sys/unix/ztypes_linux_386.go | 685 + .../x/sys/unix/ztypes_linux_amd64.go | 703 + .../golang.org/x/sys/unix/ztypes_linux_arm.go | 674 + .../x/sys/unix/ztypes_linux_arm64.go | 682 + .../x/sys/unix/ztypes_linux_mips.go | 679 + .../x/sys/unix/ztypes_linux_mips64.go | 684 + .../x/sys/unix/ztypes_linux_mips64le.go | 684 + .../x/sys/unix/ztypes_linux_mipsle.go | 679 + .../x/sys/unix/ztypes_linux_ppc64.go | 692 + .../x/sys/unix/ztypes_linux_ppc64le.go | 692 + .../x/sys/unix/ztypes_linux_s390x.go | 709 + .../x/sys/unix/ztypes_linux_sparc64.go | 666 + .../x/sys/unix/ztypes_netbsd_386.go | 396 + .../x/sys/unix/ztypes_netbsd_amd64.go | 403 + .../x/sys/unix/ztypes_netbsd_arm.go | 401 + .../x/sys/unix/ztypes_openbsd_386.go | 441 + .../x/sys/unix/ztypes_openbsd_amd64.go | 448 + .../x/sys/unix/ztypes_solaris_amd64.go | 442 + .../vendor/golang.org/x/text/.gitattributes | 10 + .../vendor/golang.org/x/text/.gitignore | 6 + .../vendor/golang.org/x/text/AUTHORS | 3 + .../vendor/golang.org/x/text/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/text/CONTRIBUTORS | 3 + .../vendor/golang.org/x/text/LICENSE | 27 + .../vendor/golang.org/x/text/PATENTS | 22 + .../vendor/golang.org/x/text/README | 23 + .../vendor/golang.org/x/text/codereview.cfg | 1 + .../vendor/golang.org/x/text/doc.go | 13 + .../vendor/golang.org/x/text/gen.go | 292 + .../vendor/golang.org/x/text/internal/gen.go | 52 + .../golang.org/x/text/internal/gen/code.go | 351 + .../golang.org/x/text/internal/gen/gen.go | 281 + .../golang.org/x/text/internal/gen_test.go | 38 + .../golang.org/x/text/internal/internal.go | 51 + .../x/text/internal/internal_test.go | 38 + .../golang.org/x/text/internal/match.go | 67 + .../golang.org/x/text/internal/match_test.go | 56 + .../golang.org/x/text/internal/tables.go | 117 + .../x/text/internal/triegen/compact.go | 58 + .../x/text/internal/triegen/data_test.go | 875 + .../internal/triegen/example_compact_test.go | 71 + .../x/text/internal/triegen/example_test.go | 148 + .../x/text/internal/triegen/gen_test.go | 68 + .../x/text/internal/triegen/print.go | 251 + .../x/text/internal/triegen/triegen.go | 494 + .../x/text/internal/ucd/example_test.go | 81 + .../golang.org/x/text/internal/ucd/ucd.go | 376 + .../x/text/internal/ucd/ucd_test.go | 105 + .../x/text/secure/bidirule/bench_test.go | 54 + .../x/text/secure/bidirule/bidirule.go | 342 + .../x/text/secure/bidirule/bidirule_test.go | 825 + .../vendor/golang.org/x/text/secure/doc.go | 6 + .../x/text/transform/examples_test.go | 37 + .../golang.org/x/text/transform/transform.go | 705 + .../x/text/transform/transform_test.go | 1317 ++ .../golang.org/x/text/unicode/bidi/bidi.go | 198 + .../golang.org/x/text/unicode/bidi/bracket.go | 335 + .../golang.org/x/text/unicode/bidi/core.go | 1058 ++ .../x/text/unicode/bidi/core_test.go | 224 + .../golang.org/x/text/unicode/bidi/gen.go | 133 + .../x/text/unicode/bidi/gen_ranges.go | 57 + .../x/text/unicode/bidi/gen_trieval.go | 64 + .../golang.org/x/text/unicode/bidi/prop.go | 206 + .../x/text/unicode/bidi/ranges_test.go | 53 + .../golang.org/x/text/unicode/bidi/tables.go | 1779 ++ .../x/text/unicode/bidi/tables_test.go | 82 + .../golang.org/x/text/unicode/bidi/trieval.go | 60 + .../golang.org/x/text/unicode/cldr/base.go | 100 + .../golang.org/x/text/unicode/cldr/cldr.go | 130 + .../x/text/unicode/cldr/cldr_test.go | 27 + .../golang.org/x/text/unicode/cldr/collate.go | 359 + .../x/text/unicode/cldr/collate_test.go | 275 + .../x/text/unicode/cldr/data_test.go | 186 + .../golang.org/x/text/unicode/cldr/decode.go | 171 + .../x/text/unicode/cldr/examples_test.go | 21 + .../golang.org/x/text/unicode/cldr/makexml.go | 400 + .../golang.org/x/text/unicode/cldr/resolve.go | 602 + .../x/text/unicode/cldr/resolve_test.go | 368 + .../golang.org/x/text/unicode/cldr/slice.go | 144 + .../x/text/unicode/cldr/slice_test.go | 175 + .../golang.org/x/text/unicode/cldr/xml.go | 1487 ++ .../vendor/golang.org/x/text/unicode/doc.go | 8 + .../x/text/unicode/norm/composition.go | 508 + .../x/text/unicode/norm/composition_test.go | 130 + .../x/text/unicode/norm/example_iter_test.go | 82 + .../x/text/unicode/norm/example_test.go | 27 + .../x/text/unicode/norm/forminfo.go | 259 + .../x/text/unicode/norm/forminfo_test.go | 54 + .../golang.org/x/text/unicode/norm/input.go | 109 + .../golang.org/x/text/unicode/norm/iter.go | 457 + .../x/text/unicode/norm/iter_test.go | 98 + .../x/text/unicode/norm/maketables.go | 976 + .../x/text/unicode/norm/norm_test.go | 14 + .../x/text/unicode/norm/normalize.go | 609 + .../x/text/unicode/norm/normalize_test.go | 1287 ++ .../x/text/unicode/norm/readwriter.go | 125 + .../x/text/unicode/norm/readwriter_test.go | 56 + .../golang.org/x/text/unicode/norm/tables.go | 7631 ++++++++ .../x/text/unicode/norm/transform.go | 88 + .../x/text/unicode/norm/transform_test.go | 101 + .../golang.org/x/text/unicode/norm/trie.go | 54 + .../golang.org/x/text/unicode/norm/triegen.go | 117 + .../x/text/unicode/norm/ucd_test.go | 275 + .../x/text/unicode/rangetable/gen.go | 113 + .../x/text/unicode/rangetable/merge.go | 260 + .../x/text/unicode/rangetable/merge_test.go | 184 + .../x/text/unicode/rangetable/rangetable.go | 70 + .../unicode/rangetable/rangetable_test.go | 55 + .../x/text/unicode/rangetable/tables.go | 5735 ++++++ .../vendor/golang.org/x/time/AUTHORS | 3 + .../vendor/golang.org/x/time/CONTRIBUTING.md | 31 + .../vendor/golang.org/x/time/CONTRIBUTORS | 3 + .../vendor/golang.org/x/time/LICENSE | 27 + .../vendor/golang.org/x/time/PATENTS | 22 + .../vendor/golang.org/x/time/README | 1 + .../vendor/golang.org/x/time/rate/rate.go | 380 + .../golang.org/x/time/rate/rate_go16.go | 21 + .../golang.org/x/time/rate/rate_go17.go | 21 + .../golang.org/x/time/rate/rate_test.go | 449 + .../vendor/google.golang.org/api/.hgignore | 11 + .../vendor/google.golang.org/api/.hgtags | 1 + .../vendor/google.golang.org/api/.travis.yml | 17 + .../vendor/google.golang.org/api/AUTHORS | 10 + .../google.golang.org/api/CONTRIBUTING.md | 484 + .../vendor/google.golang.org/api/CONTRIBUTORS | 52 + .../google.golang.org/api/GettingStarted.md | 130 + .../vendor/google.golang.org/api/LICENSE | 27 + .../vendor/google.golang.org/api/Makefile | 27 + .../vendor/google.golang.org/api/NOTES | 13 + .../vendor/google.golang.org/api/README.md | 109 + .../vendor/google.golang.org/api/TODO | 2 + .../google.golang.org/api/api-list.json | 2790 +++ .../api/cloudtrace/v1/cloudtrace-api.json | 383 + .../api/cloudtrace/v1/cloudtrace-gen.go | 958 + .../api/gensupport/backoff.go | 46 + .../api/gensupport/backoff_test.go | 46 + .../api/gensupport/buffer.go | 77 + .../api/gensupport/buffer_test.go | 296 + .../google.golang.org/api/gensupport/doc.go | 10 + .../api/gensupport/header.go | 22 + .../api/gensupport/header_test.go | 28 + .../google.golang.org/api/gensupport/json.go | 211 + .../api/gensupport/json_test.go | 516 + .../api/gensupport/jsonfloat.go | 57 + .../api/gensupport/jsonfloat_test.go | 53 + .../google.golang.org/api/gensupport/media.go | 199 + .../api/gensupport/media_test.go | 142 + .../api/gensupport/params.go | 50 + .../api/gensupport/resumable.go | 217 + .../api/gensupport/resumable_test.go | 281 + .../google.golang.org/api/gensupport/retry.go | 85 + .../api/gensupport/retry_test.go | 176 + .../google.golang.org/api/gensupport/send.go | 55 + .../api/gensupport/util_test.go | 57 + .../api/googleapi/googleapi.go | 406 + .../api/googleapi/googleapi_test.go | 291 + .../googleapi/internal/uritemplates/LICENSE | 18 + .../internal/uritemplates/uritemplates.go | 248 + .../uritemplates/uritemplates_test.go | 280 + .../googleapi/internal/uritemplates/utils.go | 17 + .../api/googleapi/transport/apikey.go | 38 + .../google.golang.org/api/googleapi/types.go | 202 + .../api/googleapi/types_test.go | 68 + .../google.golang.org/api/internal/creds.go | 38 + .../google.golang.org/api/internal/pool.go | 59 + .../api/internal/pool_test.go | 82 + .../api/internal/settings.go | 37 + .../api/iterator/examples_test.go | 227 + .../api/iterator/iterator.go | 231 + .../api/iterator/iterator_test.go | 270 + .../vendor/google.golang.org/api/key.json.enc | Bin 0 -> 2432 bytes .../google.golang.org/api/option/option.go | 156 + .../api/option/option_test.go | 66 + .../api/support/bundler/bundler.go | 258 + .../api/support/bundler/bundler_test.go | 285 + .../google.golang.org/api/transport/dial.go | 49 + .../api/transport/grpc/dial.go | 98 + .../api/transport/grpc/dial_appengine.go | 34 + .../api/transport/grpc/dial_test.go | 66 + .../api/transport/http/dial.go | 119 + .../api/transport/http/dial_appengine.go | 30 + .../google.golang.org/appengine/.travis.yml | 18 + .../google.golang.org/appengine/LICENSE | 202 + .../google.golang.org/appengine/README.md | 73 + .../google.golang.org/appengine/appengine.go | 112 + .../appengine/appengine_test.go | 49 + .../appengine/appengine_vm.go | 20 + .../google.golang.org/appengine/errors.go | 46 + .../google.golang.org/appengine/identity.go | 142 + .../appengine/internal/api.go | 646 + .../appengine/internal/api_classic.go | 159 + .../appengine/internal/api_common.go | 86 + .../appengine/internal/api_race_test.go | 9 + .../appengine/internal/api_test.go | 467 + .../appengine/internal/app_id.go | 28 + .../appengine/internal/app_id_test.go | 34 + .../app_identity/app_identity_service.pb.go | 296 + .../app_identity/app_identity_service.proto | 64 + .../appengine/internal/base/api_base.pb.go | 133 + .../appengine/internal/base/api_base.proto | 33 + .../internal/datastore/datastore_v3.pb.go | 2778 +++ .../internal/datastore/datastore_v3.proto | 541 + .../appengine/internal/identity.go | 14 + .../appengine/internal/identity_classic.go | 27 + .../appengine/internal/identity_vm.go | 97 + .../appengine/internal/internal.go | 110 + .../appengine/internal/internal_vm_test.go | 60 + .../appengine/internal/log/log_service.pb.go | 899 + .../appengine/internal/log/log_service.proto | 150 + .../appengine/internal/main.go | 15 + .../appengine/internal/main_vm.go | 44 + .../appengine/internal/metadata.go | 61 + .../internal/modules/modules_service.pb.go | 375 + .../internal/modules/modules_service.proto | 80 + .../appengine/internal/net.go | 56 + .../appengine/internal/net_test.go | 58 + .../appengine/internal/regen.sh | 40 + .../internal/remote_api/remote_api.pb.go | 231 + .../internal/remote_api/remote_api.proto | 44 + .../internal/socket/socket_service.pb.go | 1858 ++ .../internal/socket/socket_service.proto | 460 + .../appengine/internal/transaction.go | 107 + .../internal/urlfetch/urlfetch_service.pb.go | 355 + .../internal/urlfetch/urlfetch_service.proto | 64 + .../google.golang.org/appengine/namespace.go | 25 + .../appengine/namespace_test.go | 39 + .../google.golang.org/appengine/socket/doc.go | 10 + .../appengine/socket/socket_classic.go | 290 + .../appengine/socket/socket_vm.go | 64 + .../google.golang.org/appengine/timeout.go | 20 + .../appengine/urlfetch/urlfetch.go | 210 + .../google.golang.org/genproto/.travis.yml | 12 + .../genproto/CONTRIBUTING.md | 27 + .../vendor/google.golang.org/genproto/LICENSE | 202 + .../google.golang.org/genproto/README.md | 28 + .../api/annotations/annotations.pb.go | 64 + .../googleapis/api/annotations/http.pb.go | 566 + .../googleapis/api/authorization_config.pb.go | 80 + .../googleapis/api/experimental.pb.go | 56 + .../googleapis/datastore/v1/datastore.pb.go | 1423 ++ .../googleapis/datastore/v1/entity.pb.go | 762 + .../googleapis/datastore/v1/query.pb.go | 969 + .../googleapis/rpc/status/status.pb.go | 143 + .../googleapis/type/latlng/latlng.pb.go | 114 + .../google.golang.org/genproto/regen.go | 123 + .../google.golang.org/genproto/regen.sh | 77 + .../vendor/google.golang.org/grpc/.travis.yml | 20 + .../google.golang.org/grpc/CONTRIBUTING.md | 32 + .../vendor/google.golang.org/grpc/LICENSE | 28 + .../vendor/google.golang.org/grpc/Makefile | 52 + .../vendor/google.golang.org/grpc/PATENTS | 22 + .../vendor/google.golang.org/grpc/README.md | 45 + .../vendor/google.golang.org/grpc/backoff.go | 80 + .../google.golang.org/grpc/backoff_test.go | 11 + .../vendor/google.golang.org/grpc/balancer.go | 408 + .../google.golang.org/grpc/balancer_test.go | 438 + .../vendor/google.golang.org/grpc/call.go | 324 + .../google.golang.org/grpc/call_test.go | 299 + .../google.golang.org/grpc/clientconn.go | 1080 ++ .../google.golang.org/grpc/clientconn_test.go | 340 + .../vendor/google.golang.org/grpc/codec.go | 119 + .../grpc/codec_benchmark_test.go | 115 + .../google.golang.org/grpc/codec_test.go | 143 + .../vendor/google.golang.org/grpc/codegen.sh | 17 + .../grpc/codes/code_string.go | 16 + .../google.golang.org/grpc/codes/codes.go | 159 + .../vendor/google.golang.org/grpc/coverage.sh | 48 + .../grpc/credentials/credentials.go | 234 + .../grpc/credentials/credentials_test.go | 222 + .../grpc/credentials/credentials_util_go17.go | 75 + .../grpc/credentials/credentials_util_go18.go | 53 + .../credentials/credentials_util_pre_go17.go | 72 + .../grpc/credentials/oauth/oauth.go | 188 + .../vendor/google.golang.org/grpc/doc.go | 6 + .../vendor/google.golang.org/grpc/go16.go | 113 + .../vendor/google.golang.org/grpc/go17.go | 113 + .../vendor/google.golang.org/grpc/grpclb.go | 765 + .../grpc/grpclb/grpc_lb_v1/grpclb.pb.go | 629 + .../grpc/grpclb/grpc_lb_v1/grpclb.proto | 179 + .../grpc/grpclb/grpclb_server_generated.go | 54 + .../grpc/grpclb/grpclb_test.go | 933 + .../google.golang.org/grpc/grpclog/logger.go | 93 + .../google.golang.org/grpc/interceptor.go | 90 + .../grpc/internal/internal.go | 49 + .../grpc/keepalive/keepalive.go | 80 + .../grpc/metadata/metadata.go | 144 + .../grpc/metadata/metadata_test.go | 86 + .../google.golang.org/grpc/naming/naming.go | 74 + .../google.golang.org/grpc/peer/peer.go | 66 + .../vendor/google.golang.org/grpc/proxy.go | 145 + .../google.golang.org/grpc/proxy_test.go | 192 + .../vendor/google.golang.org/grpc/rpc_util.go | 533 + .../google.golang.org/grpc/rpc_util_test.go | 251 + .../vendor/google.golang.org/grpc/server.go | 1145 ++ .../google.golang.org/grpc/server_test.go | 113 + .../google.golang.org/grpc/stats/handlers.go | 79 + .../google.golang.org/grpc/stats/stats.go | 223 + .../grpc/stats/stats_test.go | 1260 ++ .../google.golang.org/grpc/status/status.go | 145 + .../grpc/status/status_test.go | 129 + .../vendor/google.golang.org/grpc/stream.go | 656 + .../vendor/google.golang.org/grpc/tap/tap.go | 54 + .../vendor/google.golang.org/grpc/trace.go | 119 + .../grpc/transport/control.go | 251 + .../google.golang.org/grpc/transport/go16.go | 59 + .../google.golang.org/grpc/transport/go17.go | 60 + .../grpc/transport/handler_server.go | 407 + .../grpc/transport/handler_server_test.go | 399 + .../grpc/transport/http2_client.go | 1314 ++ .../grpc/transport/http2_server.go | 1099 ++ .../grpc/transport/http_util.go | 613 + .../grpc/transport/http_util_test.go | 190 + .../grpc/transport/transport.go | 682 + .../grpc/transport/transport_test.go | 2177 +++ 1386 files changed, 398453 insertions(+) create mode 100644 examples/servers/reading-list/.drone.yml create mode 100644 examples/servers/reading-list/.kube.yml create mode 100644 examples/servers/reading-list/Gopkg.lock create mode 100644 examples/servers/reading-list/Gopkg.toml create mode 100644 examples/servers/reading-list/README.md create mode 100755 examples/servers/reading-list/cloud-endpoints/create_ce.sh create mode 100644 examples/servers/reading-list/cloud-endpoints/service-ce-prd.yaml create mode 100644 examples/servers/reading-list/cmd/cli/README.md create mode 100644 examples/servers/reading-list/cmd/cli/main.go create mode 100644 examples/servers/reading-list/db.go create mode 100755 examples/servers/reading-list/gen-proto.sh create mode 100644 examples/servers/reading-list/get.go create mode 100644 examples/servers/reading-list/http_client.go create mode 100644 examples/servers/reading-list/put.go create mode 100644 examples/servers/reading-list/server/Dockerfile create mode 100644 examples/servers/reading-list/server/main.go create mode 100755 examples/servers/reading-list/server/run_local.sh create mode 100644 examples/servers/reading-list/service.go create mode 100644 examples/servers/reading-list/service.pb create mode 100644 examples/servers/reading-list/service.pb.go create mode 100644 examples/servers/reading-list/service.proto create mode 100644 examples/servers/reading-list/service.yaml create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/.travis.yml create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/AUTHORS create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/LICENSE create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/README.md create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/appveyor.yml create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/authexample_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/cloud.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/doc.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/errors.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/example_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/integration_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/prop.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/datastore/transaction.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext_test.go create mode 100755 examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/update_version.sh create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/key.json.enc create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/license_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/old-news.md create mode 100755 examples/servers/reading-list/vendor/cloud.google.com/go/run-tests.sh create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/http.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/http_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/httpexample_test.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/sampling.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace.go create mode 100644 examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CODE_OF_CONDUCT.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/LICENSE.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/config.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/flags.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/metrics/metrics.go create mode 100755 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/coverage.sh create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/gitcookies.sh.enc create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/config.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_gorilla.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_native.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server_go18.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/config.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_pb_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.proto create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.yaml create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/log.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/response.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/router.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server_gae.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/service.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_go17.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_pre_go17.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/rpc_server.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/server.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_go17.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_pre_go17.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_go17_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_pre_go17_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/tcp.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_gorilla.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_native.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate.go create mode 100644 examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram.go create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/numerichistogram.go create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/sample_data_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/weightedhistogram.go create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/exampledata.txt create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream.go create mode 100644 examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/ROADMAP.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/circle.yml create mode 100755 examples/servers/reading-list/vendor/github.com/go-kit/kit/coverage.bash create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/docker-compose-integration.yml create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint_example_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/go-kit/kit/lint create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/benchmark_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/concurrency_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/stdlib.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/stdlib_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/sync.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/sync_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/value.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/log/value_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/discard/discard.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/expvar/expvar.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/expvar/expvar_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/generic/generic.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/generic/generic_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/graphite/graphite.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/graphite/graphite_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/influx/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/influx/influx.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/influx/influx_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/internal/lv/space.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/internal/lv/space_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/internal/ratemap/ratemap.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/metrics.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/prometheus/prometheus.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/prometheus/prometheus_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/discard.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/dogstatsd.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/expvar.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/graphite.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/influx.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/prometheus.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/provider.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/provider/statsd.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/statsd/statsd.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/statsd/statsd_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/timer.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/metrics/timer_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/benchmark_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/endpoint_cache.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/endpoint_cache_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/endpointer.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/endpointer_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/factory.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/instancer.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/balancer.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/random.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/random_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/retry.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/retry_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/round_robin.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/lb/round_robin_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/sd/registrar.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/client.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/client_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/encode_decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/request_response_funcs.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/request_response_funcs_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/server.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/transport/http/server_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/go-kit/kit/update_deps.bash create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/util/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/util/conn/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/util/conn/manager.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-kit/kit/util/conn/manager_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/decode-bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/decode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/encode.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/encode_internal_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/encode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/fuzz.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-logfmt/logfmt/jsonstring.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/LICENSE.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/format_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/stack.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/stack_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/go-stack/stack/stackinternal_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/AUTHORS create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/Make.protobuf create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/all_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/any_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/clone.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/clone_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/decode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/encode.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/encode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/equal.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/equal_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/extensions.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/extensions_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/lib.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/map_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/message_set.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/message_set_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/pointer_reflect.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/pointer_unsafe.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/properties.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/proto3_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/size2_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/size_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/text.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/text_parser.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/text_parser_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/proto/text_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/link_grpc.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/protoc-gen-go/main.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/any.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/any/any.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/any/any.proto create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/any_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/duration.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/duration_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/regen.sh create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/struct/struct.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/struct/struct.proto create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/timestamp.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/timestamp/timestamp.proto create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/timestamp_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/wrappers/wrappers.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/golang/protobuf/ptypes/wrappers/wrappers.proto create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/context.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/context_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/context/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/canonical.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/canonical_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/compress.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/compress_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/cors.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/cors_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/handlers.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/handlers_go18.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/handlers_go18_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/handlers_pre18.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/handlers_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/proxy_headers.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/proxy_headers_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/recovery.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/handlers/recovery_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/context_gorilla.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/context_gorilla_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/context_native.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/context_native_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/mux.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/mux_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/old_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/regexp.go create mode 100644 examples/servers/reading-list/vendor/github.com/gorilla/mux/route.go create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/DOC.md create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain.go create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/chain_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/checkup.sh create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/doc.go create mode 100755 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/fixup.sh create mode 100755 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/test_all.sh create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers.go create mode 100644 examples/servers/reading-list/vendor/github.com/grpc-ecosystem/go-grpc-middleware/wrappers_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/CHANGELOG.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/GNUmakefile create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/ISSUE_TEMPLATE.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/acl.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/acl_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/agent.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/agent_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/api.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/api_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/catalog.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/catalog_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/coordinate.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/coordinate_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/event.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/event_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/health.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/health_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/kv.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/kv_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/lock.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/lock_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_area.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_autopilot.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_autopilot_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_keyring.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_keyring_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_raft.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/operator_raft_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/prepared_query.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/prepared_query_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/raw.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/semaphore.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/semaphore_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/session.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/session_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/snapshot.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/snapshot_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/status.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/api/status_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/main.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/consul/main_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-cleanhttp/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-cleanhttp/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-cleanhttp/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/rootcerts.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/rootcerts_base.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/rootcerts_darwin.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/rootcerts_darwin_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/go-rootcerts/rootcerts_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/CHANGELOG.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/GNUmakefile create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/client.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/client_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/config.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/coordinate.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/coordinate_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/performance_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/phantom.go create mode 100644 examples/servers/reading-list/vendor/github.com/hashicorp/serf/coordinate/util_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/.dockerignore create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/.mention-bot create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/CHANGELOG.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/CODING_GUIDELINES.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Dockerfile_build_ubuntu32 create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Dockerfile_build_ubuntu64 create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Dockerfile_build_ubuntu64_git create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Dockerfile_test_ubuntu32 create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Godeps create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/LICENSE_OF_DEPENDENCIES.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/QUERIES.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/TODO.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/appveyor.yml create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/build.py create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/build.sh create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/circle-test.sh create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/circle.yml create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/influxdb.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/influxdb_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/v2/client.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/v2/client_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/v2/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/client/v2/udp.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/errors.go create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/gobuild.sh create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/influxdb.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/consistency.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/inline_fnv.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/inline_fnv_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/inline_strconv_parse.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/inline_strconv_parse_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/points.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/points_internal_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/points_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/rows.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/statistic.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/statistic_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/models/time.go create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/nightly.sh create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/node.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/pkg/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/pkg/escape/bytes.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/pkg/escape/bytes_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/pkg/escape/strings.go create mode 100644 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/pkg/escape/strings_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/influxdata/influxdb/test.sh create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/path.go create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/path_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/router.go create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/router_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/tree.go create mode 100644 examples/servers/reading-list/vendor/github.com/julienschmidt/httprouter/tree_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/MAINTAINERS create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/env_os.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/env_syscall.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/envconfig.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/envconfig_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/usage.go create mode 100644 examples/servers/reading-list/vendor/github.com/kelseyhightower/envconfig/usage_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/Readme create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/decode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/scanner.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/scanner_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/kr/logfmt/unquote.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/NOTICE create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/all_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/decode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/encode.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/encode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/matttproud/golang_protobuf_extensions/pbutil/fixtures_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/mitchellh/go-homedir/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/mitchellh/go-homedir/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/mitchellh/go-homedir/homedir.go create mode 100644 examples/servers/reading-list/vendor/github.com/mitchellh/go-homedir/homedir_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/COPYING create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/uuid.go create mode 100644 examples/servers/reading-list/vendor/github.com/nu7hatch/gouuid/uuid_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/appveyor.yml create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/errors.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/errors_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/example_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/format_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/stack.go create mode 100644 examples/servers/reading-list/vendor/github.com/pkg/errors/stack_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/AUTHORS.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/CHANGELOG.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/NOTICE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/VERSION create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/collector.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/counter.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/counter_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/desc.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/example_clustermanager_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/examples_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/fnv.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/gauge.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/histogram.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/http.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/http_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/metric.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/metric_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/registry.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/registry_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/summary.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/summary_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/untyped.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/value.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/vec.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_golang/prometheus/vec_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/MAINTAINERS.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/NOTICE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/go/metrics.pb.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/metrics.proto create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/pom.xml create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/client_model/setup.py create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/MAINTAINERS.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/NOTICE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/decode.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/decode_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/encode.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/expfmt.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/fuzz.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/text_create.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/text_create_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/text_parse.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/expfmt/text_parse_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/alert.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/alert_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/fingerprinting.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/fnv.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/labels.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/labels_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/labelset.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/metric.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/metric_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/model.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/signature.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/signature_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/silence.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/silence_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/time.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/time_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/value.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/common/model/value_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/MAINTAINERS.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/Makefile create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/NOTICE create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/buddyinfo.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/buddyinfo_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/fs.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/fs_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/ipvs.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/ipvs_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/mdstat.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/mdstat_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/mountstats.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/mountstats_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_io.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_io_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_limits.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_limits_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_stat.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_stat_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/proc_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/stat.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/stat_test.go create mode 100755 examples/servers/reading-list/vendor/github.com/prometheus/procfs/ttar create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/xfrm.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/xfrm_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/xfs/parse.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/xfs/parse_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/prometheus/procfs/xfs/xfs.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/.gitignore create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/.travis.yml create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/CHANGELOG.md create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/LICENSE create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/README.md create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/alt_exit.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/alt_exit_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/doc.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/entry.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/entry_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/exported.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/formatter.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/formatter_bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/hook_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/hooks.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/json_formatter.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/json_formatter_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/logger.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/logger_bench_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/logrus.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/logrus_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_appengine.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_bsd.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_linux.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_notwindows.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_solaris.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/terminal_windows.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/text_formatter.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/text_formatter_test.go create mode 100644 examples/servers/reading-list/vendor/github.com/sirupsen/logrus/writer.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/.gitattributes create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/.gitignore create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/PATENTS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/codereview.cfg create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/context.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/context_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_17_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/go19.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/pre_go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/pre_go19.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/context/withtimeout_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/.gitignore create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/Dockerfile create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/Makefile create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/ciphers.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/ciphers_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/client_conn_pool.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/configure_transport.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/databuffer.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/databuffer_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/errors.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/errors_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/flow.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/flow_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/frame.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/frame_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go16.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go17_not18.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go18.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go18_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go19.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/go19_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/gotrack.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/gotrack_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/headermap.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/encode.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/encode_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/hpack.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/hpack_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/huffman.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/hpack/tables_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/http2.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/http2_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/not_go16.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/not_go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/not_go18.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/not_go19.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/pipe.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/pipe_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/server.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/server_push_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/server_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/transport.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/transport_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/write.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched_priority.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched_priority_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched_random.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched_random_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/writesched_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/http2/z_spec_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/idna.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/idna_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/punycode.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/punycode_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/trie.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/idna/trieval.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/internal/timeseries/timeseries.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/internal/timeseries/timeseries_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/lex/httplex/httplex.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/lex/httplex/httplex_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/events.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/histogram.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/histogram_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/trace.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/trace_go16.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/trace_go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/net/trace/trace_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/.travis.yml create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/README.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/client_appengine.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/appengine.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/appengine_hook.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/appengineflex_hook.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/default.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/google.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/google_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/jwt.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/jwt_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/sdk.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/google/sdk_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/oauth2.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/oauth2_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/token.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/token_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/transport.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/internal/transport_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/jws/jws.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/jws/jws_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/jwt/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/jwt/jwt.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/jwt/jwt_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/oauth2.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/oauth2_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/token.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/token_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/transport.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/oauth2/transport_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/PATENTS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/codereview.cfg create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/semaphore/semaphore.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/semaphore/semaphore_bench_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sync/semaphore/semaphore_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/.gitattributes create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/.gitignore create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/PATENTS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/codereview.cfg create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/.gitignore create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/README.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_darwin_386.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_darwin_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_darwin_arm.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_darwin_arm64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_dragonfly_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_freebsd_386.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_freebsd_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_freebsd_arm.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_386.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_arm.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_arm64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_mips64x.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_mipsx.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_linux_s390x.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_netbsd_386.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_netbsd_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_netbsd_arm.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_openbsd_386.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_openbsd_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/asm_solaris_amd64.s create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/bluetooth_linux.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/constants.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/creds_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/dirent.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/endian_big.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/endian_little.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/env_unix.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/env_unset.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/export_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/flock.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/flock_linux_32bit.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/gccgo.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/gccgo_c.c create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/gccgo_linux_sparc64.go create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mkall.sh create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mkerrors.sh create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mkpost.go create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksyscall.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksyscall_solaris.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysctl_openbsd.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysnum_darwin.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysnum_dragonfly.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysnum_freebsd.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysnum_netbsd.pl create mode 100755 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mksysnum_openbsd.pl create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/mmap_unix_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/openbsd_pledge.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/openbsd_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/race.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/race0.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/sockcmsg_linux.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/sockcmsg_unix.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/str.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_bsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_bsd_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_darwin.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_darwin_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_darwin_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_dragonfly.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_freebsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_freebsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_freebsd_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_linux_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_netbsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_netbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_no_getwd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_openbsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_openbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_solaris.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_solaris_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_unix.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_unix_gc.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/syscall_unix_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_darwin.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_dragonfly.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_freebsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_netbsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_openbsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/types_solaris.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_darwin_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_darwin_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysctl_openbsd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_darwin_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_darwin_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/zsysnum_solaris_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_darwin_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_darwin_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/.gitattributes create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/.gitignore create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/PATENTS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/codereview.cfg create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/doc.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/gen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/gen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/gen/code.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/gen/gen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/gen_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/internal.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/internal_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/match.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/match_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/compact.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/data_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/example_compact_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/gen_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/print.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/triegen/triegen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/ucd/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/ucd/ucd.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/internal/ucd/ucd_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/secure/bidirule/bench_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/secure/bidirule/bidirule.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/secure/bidirule/bidirule_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/secure/doc.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/transform/examples_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/transform/transform.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/transform/transform_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/bidi.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/bracket.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/core.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/core_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/gen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/gen_ranges.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/gen_trieval.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/prop.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/ranges_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/tables_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/bidi/trieval.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/base.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/cldr.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/cldr_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/collate.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/collate_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/data_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/decode.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/examples_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/makexml.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/resolve.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/resolve_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/slice.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/slice_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/cldr/xml.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/doc.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/composition.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/composition_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/example_iter_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/example_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/forminfo.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/forminfo_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/input.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/iter.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/iter_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/maketables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/norm_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/normalize.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/normalize_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/readwriter.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/readwriter_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/transform.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/transform_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/trie.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/triegen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/norm/ucd_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/gen.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/merge.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/merge_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/rangetable.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/rangetable_test.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/text/unicode/rangetable/tables.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/AUTHORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/LICENSE create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/PATENTS create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/README create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/rate/rate.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/rate/rate_go16.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/rate/rate_go17.go create mode 100644 examples/servers/reading-list/vendor/golang.org/x/time/rate/rate_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/.hgignore create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/.hgtags create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/.travis.yml create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/AUTHORS create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/CONTRIBUTORS create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/GettingStarted.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/LICENSE create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/Makefile create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/NOTES create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/README.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/TODO create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/api-list.json create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/cloudtrace/v1/cloudtrace-api.json create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/cloudtrace/v1/cloudtrace-gen.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/backoff.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/backoff_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/buffer.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/buffer_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/doc.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/header.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/header_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/json.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/json_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/jsonfloat.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/jsonfloat_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/media.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/media_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/params.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/resumable.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/resumable_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/retry.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/retry_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/send.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/gensupport/util_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/googleapi.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/googleapi_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/internal/uritemplates/LICENSE create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/internal/uritemplates/uritemplates_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/internal/uritemplates/utils.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/transport/apikey.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/types.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/googleapi/types_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/internal/creds.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/internal/pool.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/internal/pool_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/internal/settings.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/iterator/examples_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/iterator/iterator.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/iterator/iterator_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/key.json.enc create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/option/option.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/option/option_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/support/bundler/bundler.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/support/bundler/bundler_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/dial.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/grpc/dial.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/grpc/dial_appengine.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/grpc/dial_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/http/dial.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/api/transport/http/dial_appengine.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/.travis.yml create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/LICENSE create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/README.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/appengine.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/appengine_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/appengine_vm.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/errors.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/identity.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/api.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/api_classic.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/api_common.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/api_race_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/api_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/app_id.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/app_id_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/app_identity/app_identity_service.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/base/api_base.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/base/api_base.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/datastore/datastore_v3.pb.go create mode 100755 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/datastore/datastore_v3.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/identity.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/identity_classic.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/identity_vm.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/internal.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/internal_vm_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/log/log_service.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/log/log_service.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/main.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/main_vm.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/metadata.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/modules/modules_service.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/modules/modules_service.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/net.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/net_test.go create mode 100755 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/regen.sh create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/remote_api/remote_api.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/remote_api/remote_api.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/socket/socket_service.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/socket/socket_service.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/transaction.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/namespace.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/namespace_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/socket/doc.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/socket/socket_classic.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/socket/socket_vm.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/timeout.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/appengine/urlfetch/urlfetch.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/.travis.yml create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/LICENSE create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/README.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/api/annotations/annotations.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/api/annotations/http.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/api/authorization_config.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/api/experimental.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/datastore/v1/datastore.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/datastore/v1/entity.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/datastore/v1/query.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/rpc/status/status.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/googleapis/type/latlng/latlng.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/genproto/regen.go create mode 100755 examples/servers/reading-list/vendor/google.golang.org/genproto/regen.sh create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/.travis.yml create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/CONTRIBUTING.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/LICENSE create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/Makefile create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/PATENTS create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/README.md create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/backoff.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/backoff_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/balancer.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/balancer_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/call.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/call_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/clientconn.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/clientconn_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/codec.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/codec_benchmark_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/codec_test.go create mode 100755 examples/servers/reading-list/vendor/google.golang.org/grpc/codegen.sh create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/codes/code_string.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/codes/codes.go create mode 100755 examples/servers/reading-list/vendor/google.golang.org/grpc/coverage.sh create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/credentials.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/credentials_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/credentials_util_go17.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/credentials_util_go18.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/credentials_util_pre_go17.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/credentials/oauth/oauth.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/doc.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/go16.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/go17.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/grpclb.pb.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclb/grpc_lb_v1/grpclb.proto create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclb/grpclb_server_generated.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclb/grpclb_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/grpclog/logger.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/interceptor.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/internal/internal.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/keepalive/keepalive.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/metadata/metadata.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/metadata/metadata_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/naming/naming.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/peer/peer.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/proxy.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/proxy_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/rpc_util.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/rpc_util_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/server.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/server_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/stats/handlers.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/stats/stats.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/stats/stats_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/status/status.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/status/status_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/stream.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/tap/tap.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/trace.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/control.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/go16.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/go17.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/handler_server.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/handler_server_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/http2_client.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/http2_server.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/http_util.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/http_util_test.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/transport.go create mode 100644 examples/servers/reading-list/vendor/google.golang.org/grpc/transport/transport_test.go diff --git a/examples/servers/reading-list/.drone.yml b/examples/servers/reading-list/.drone.yml new file mode 100644 index 000000000..635328d49 --- /dev/null +++ b/examples/servers/reading-list/.drone.yml @@ -0,0 +1,58 @@ +# See: https://github.com/drone/drone/blob/v0.4.0/docs/build/README.md +build: + + test: + image: golang:1.8 + environment: + - GOPATH=/drone + commands: + - go test -v . + when: + event: [push, pull_request] + + build: + image: golang:1.8 + environment: + - GOPATH=/drone + commands: + - cd server + # copy cert from host + - cp /etc/ssl/certs/ca-certificates.crt . + - CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server . + when: + event: tag + +publish: + # build the Docker image and publish it to GCR + gcr: + repo: nyt-reading-list/server + tag: "$$TAG" + context: ./server + file: server/Dockerfile + token: > + $$GOOGLE_CREDENTIALS + storage_driver: overlay + when: + event: tag + +deploy: + # apply the deployment changes to the GKE cluster + gke: + image: nytimes/drone-gke + project: nyt-reading-list + zone: us-central1-b + template: .kube.yml + cluster: central1 + token: > + $$GOOGLE_CREDENTIALS + vars: + GCP_PROJECT_ID: nyt-reading-list + ENDPOINTS_VERSION: 2017-07-10r0 + when: + event: tag + +notify: + slack: + webhook_url: $$SLACK_WEBHOOK_URL + username: drone + channel: games-releases diff --git a/examples/servers/reading-list/.kube.yml b/examples/servers/reading-list/.kube.yml new file mode 100644 index 000000000..59697c83d --- /dev/null +++ b/examples/servers/reading-list/.kube.yml @@ -0,0 +1,97 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: reading-list-server +spec: + minReadySeconds: 30 + replicas: 5 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 2 + template: + metadata: + labels: + app: reading-list-server + spec: + terminationGracePeriodSeconds: 60 + volumes: + - name: nyt-reading-list-tls + secret: + secretName: nyt-reading-list-tls + containers: + - name: esp + image: "gcr.io/endpoints-release/endpoints-runtime:1.3.0" + args: [ + "-S", "443", + "-s", "nyt-reading-list.endpoints.{{.GCP_PROJECT_ID}}.cloud.goog", + "-v", "{{ .ENDPOINTS_VERSION }}", + "-a", "grpc://127.0.0.1:8081", + "-z", "healthz" + ] + ports: + - containerPort: 443 + protocol: TCP + lifecycle: + preStop: + exec: + command: ["/bin/sleep","60"] + volumeMounts: + - mountPath: /etc/nginx/ssl + readOnly: true + name: nyt-reading-list-tls + readinessProbe: + httpGet: + path: /healthz + port: 443 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /healthz + port: 443 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + - name: reading-list-server + image: gcr.io/nyt-games-dev/reading-list-server:{{.COMMIT}} + ports: + - containerPort: 8081 + protocol: TCP + - containerPort: 8080 + protocol: TCP + readinessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + env: + - name: GCP_PROJECT_ID + value: {{.GCP_PROJECT_ID}} +--- +apiVersion: v1 +kind: Service +metadata: + name: reading-list-server + labels: + app: reading-list-server +spec: + type: LoadBalancer + ports: + - name: reading-list-server + protocol: TCP + port: 443 + selector: + app: reading-list-server diff --git a/examples/servers/reading-list/Gopkg.lock b/examples/servers/reading-list/Gopkg.lock new file mode 100644 index 000000000..bc7061e03 --- /dev/null +++ b/examples/servers/reading-list/Gopkg.lock @@ -0,0 +1,249 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "cloud.google.com/go" + packages = ["compute/metadata","datastore","internal/atomiccache","internal/fields","internal/tracecontext","internal/version","trace"] + revision = "a5913b3f7deecba45e98ff33cefbac4fd204ddd7" + version = "v0.10.0" + +[[projects]] + branch = "master" + name = "github.com/NYTimes/gizmo" + packages = ["config","config/metrics","server","server/kit","web"] + revision = "1c0e1e9ca7a78cccba0be9bbb930543a186591f7" + +[[projects]] + branch = "master" + name = "github.com/NYTimes/logrotate" + packages = ["."] + revision = "a5276429b5aadf067486dcb082e41a600c757d2c" + +[[projects]] + name = "github.com/VividCortex/gohistogram" + packages = ["."] + revision = "51564d9861991fb0ad0f531c99ef602d0f9866e6" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" + +[[projects]] + name = "github.com/go-kit/kit" + packages = ["endpoint","log","metrics","metrics/discard","metrics/dogstatsd","metrics/expvar","metrics/generic","metrics/graphite","metrics/influx","metrics/internal/lv","metrics/internal/ratemap","metrics/prometheus","metrics/provider","metrics/statsd","sd","sd/lb","transport/http","util/conn"] + revision = "a9ca6725cbbea455e61c6bc8a1ed28e81eb3493b" + version = "v0.5.0" + +[[projects]] + name = "github.com/go-logfmt/logfmt" + packages = ["."] + revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" + version = "v0.3.0" + +[[projects]] + name = "github.com/go-stack/stack" + packages = ["."] + revision = "54be5f394ed2c3e19dac9134a40a95ba5a017f7b" + version = "v1.5.4" + +[[projects]] + branch = "master" + name = "github.com/golang/protobuf" + packages = ["proto","protoc-gen-go/descriptor","ptypes/any","ptypes/struct","ptypes/timestamp","ptypes/wrappers"] + revision = "0a4f71a498b7c4812f64969510bcb4eca251e33a" + +[[projects]] + name = "github.com/gorilla/context" + packages = ["."] + revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" + version = "v1.1" + +[[projects]] + name = "github.com/gorilla/handlers" + packages = ["."] + revision = "a4043c62cc2329bacda331d33fc908ab11ef0ec3" + version = "v1.2.1" + +[[projects]] + name = "github.com/gorilla/mux" + packages = ["."] + revision = "bcd8bc72b08df0f70df986b97f95590779502d31" + version = "v1.4.0" + +[[projects]] + branch = "master" + name = "github.com/grpc-ecosystem/go-grpc-middleware" + packages = ["."] + revision = "f63a7dfb64c138bd93d5c5b896d8b33c4b08e000" + +[[projects]] + name = "github.com/hashicorp/consul" + packages = ["api"] + revision = "2c7715154d8d4568524b76d2d4deb7ca6fd1b285" + version = "v0.8.5" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-cleanhttp" + packages = ["."] + revision = "3573b8b52aa7b37b9358d966a898feb387f62437" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-rootcerts" + packages = ["."] + revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00" + +[[projects]] + name = "github.com/hashicorp/serf" + packages = ["coordinate"] + revision = "d6574a5bb1226678d7010325fb6c985db20ee458" + version = "v0.8.1" + +[[projects]] + name = "github.com/influxdata/influxdb" + packages = ["client/v2","models","pkg/escape"] + revision = "76124df5c121e411e99807b9473a03eb785cd43b" + version = "v1.3.0" + +[[projects]] + branch = "master" + name = "github.com/julienschmidt/httprouter" + packages = ["."] + revision = "975b5c4c7c21c0e3d2764200bf2aa8e34657ae6e" + +[[projects]] + name = "github.com/kelseyhightower/envconfig" + packages = ["."] + revision = "f611eb38b3875cc3bd991ca91c51d06446afa14c" + version = "v1.3.0" + +[[projects]] + branch = "master" + name = "github.com/kr/logfmt" + packages = ["."] + revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" + +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + revision = "b8bc1bf767474819792c23f32d8286a45736f1c6" + +[[projects]] + branch = "master" + name = "github.com/nu7hatch/gouuid" + packages = ["."] + revision = "179d4d0c4d8d407a32af483c2354df1d2c91e6c3" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/prometheus/client_golang" + packages = ["prometheus"] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "6f3806018612930941127f2a7c6c453ba2c527d2" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] + revision = "3e6a7635bac6573d43f49f97b47eb9bda195dba8" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [".","xfs"] + revision = "e645f4e5aaa8506fc71d6edbc5c4ff02c04c46f2" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "a3f95b5c423586578a4e099b11a46c2479628cac" + version = "1.0.2" + +[[projects]] + branch = "master" + name = "golang.org/x/net" + packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"] + revision = "5d0d7096b5f2ef7ba83f319c07ef63989a1c6f91" + +[[projects]] + branch = "master" + name = "golang.org/x/oauth2" + packages = [".","google","internal","jws","jwt"] + revision = "cce311a261e6fcf29de72ca96827bdb0b7d9c9e6" + +[[projects]] + branch = "master" + name = "golang.org/x/sync" + packages = ["semaphore"] + revision = "f52d1811a62927559de87708c8913c1650ce4f26" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix"] + revision = "cd2c276457edda6df7fb04895d3fd6a6add42926" + +[[projects]] + branch = "master" + name = "golang.org/x/text" + packages = ["internal/gen","internal/triegen","internal/ucd","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"] + revision = "836efe42bb4aa16aaa17b9c155d8813d336ed720" + +[[projects]] + branch = "master" + name = "golang.org/x/time" + packages = ["rate"] + revision = "8be79e1e0910c292df4e79c241bb7e8f7e725959" + +[[projects]] + branch = "master" + name = "google.golang.org/api" + packages = ["cloudtrace/v1","gensupport","googleapi","googleapi/internal/uritemplates","googleapi/transport","internal","iterator","option","support/bundler","transport","transport/grpc","transport/http"] + revision = "295e4bb0ade057ae2cfb9876ab0b54635dbfcea4" + +[[projects]] + name = "google.golang.org/appengine" + packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/socket","internal/urlfetch","socket","urlfetch"] + revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "google.golang.org/genproto" + packages = ["googleapis/api/annotations","googleapis/datastore/v1","googleapis/rpc/status","googleapis/type/latlng"] + revision = "b0a3dcfcd1a9bd48e63634bd8802960804cf8315" + +[[projects]] + name = "google.golang.org/grpc" + packages = [".","codes","credentials","credentials/oauth","grpclb/grpc_lb_v1","grpclog","internal","keepalive","metadata","naming","peer","stats","status","tap","transport"] + revision = "b15215fb911b24a5d61d57feec4233d610530464" + version = "v1.4.2" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "87a28399c4aeea5fb6637075c9daccb025df131b810a741484fd96752174a02d" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/examples/servers/reading-list/Gopkg.toml b/examples/servers/reading-list/Gopkg.toml new file mode 100644 index 000000000..6f7716fcf --- /dev/null +++ b/examples/servers/reading-list/Gopkg.toml @@ -0,0 +1,34 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" + +[[constraint]] + name = "google.golang.org/grpc" + version = "1.4.2" + +[[override]] + name = "github.com/julienschmidt/httprouter" + branch = "master" diff --git a/examples/servers/reading-list/README.md b/examples/servers/reading-list/README.md new file mode 100644 index 000000000..d385f3f70 --- /dev/null +++ b/examples/servers/reading-list/README.md @@ -0,0 +1,26 @@ +# The 'Reading List' Example + +This example implements a clone of NYT's 'saved articles API' that allows users to save, delete and retrieve nytimes.com article URLs. + +Instead of utilizing NYT's auth, this example leans on Google OAuth for user identity. + +To run locally, have the latest version of `gcloud` installed and execute the `./server/run_local.sh` script to start up the Datastore emulater and the reading list server. + +A few highlights of this service worth calling out: + +* [service.yaml](service.yaml) + * An Open API specification that describes the endpoints in this service. +* [gen-proto.sh](gen-proto.sh) + * A script that relies on github.com/NYTimes/openapi2proto to generate a gRPC service spec with HTTP annotations from the Open API spec along with the Go/Cloud Endpoint stubs via protoc. +* [service.go](service.go) + * The actual [kit.Service](http://godoc.org/github.com/NYTimes/gizmo/server/kit#Service) implementation. +* [http_client.go](http_client.go) + * A go-kit client for programmatically accessing the API via HTTP/JSON. +* [cmd/cli/main.go](cmd/cli/main.go) + * A CLI wrapper around the gRPC client. +* [.drone.yaml](.drone.yaml) + * An example configuration file for [Drone CI](http://readme.drone.io/) using the [NYTimes/drone-gke](https://github.com/nytimes/drone-gke) plugin for managing automated deployments to Google Container Engine. +* [cloud-endpoints/service-ce-prd.yaml](cloud-endpoints/service-ce-prd.yaml) + * A service configuration for Google Cloud Endpoints. + +This example [mirrors an example](https://github.com/NYTimes/marvin/tree/master/examples/reading-list#the-reading-list-example) in gizmo's sibling server for Google App Engine, marvin. diff --git a/examples/servers/reading-list/cloud-endpoints/create_ce.sh b/examples/servers/reading-list/cloud-endpoints/create_ce.sh new file mode 100755 index 000000000..a0d77cb5d --- /dev/null +++ b/examples/servers/reading-list/cloud-endpoints/create_ce.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +gcloud service-management deploy ../service.pb ./service-ce-$1.yaml diff --git a/examples/servers/reading-list/cloud-endpoints/service-ce-prd.yaml b/examples/servers/reading-list/cloud-endpoints/service-ce-prd.yaml new file mode 100644 index 000000000..015e86e04 --- /dev/null +++ b/examples/servers/reading-list/cloud-endpoints/service-ce-prd.yaml @@ -0,0 +1,13 @@ +type: google.api.Service +config_version: 3 + +name: reading-list.endpoints.nyt-reading-list.cloud.goog + +title: Save, retrieve and delete nytimes.com URLs +apis: +- name: readinglist.ReadingListServiceServer + +usage: + rules: + - selector: "*" + allow_unregistered_calls: true diff --git a/examples/servers/reading-list/cmd/cli/README.md b/examples/servers/reading-list/cmd/cli/README.md new file mode 100644 index 000000000..05c8f5257 --- /dev/null +++ b/examples/servers/reading-list/cmd/cli/README.md @@ -0,0 +1,29 @@ +# The Reading List CLI + +Use `gcloud auth application-default login` to generate credentials. + +Alternatively, you can use the `-creds` flag that points to the path of a Google service account JSON key file. + +If running locally, use `-insecure` and `-fakeID` to inject user identity. + +## Usage + +``` +Usage of ./cli: + -creds string + the path of the service account credentials file. if empty, uses Google Application Default Credentials. + -delete + delete this URL from the list (requires -mode update) + -fakeID string + for local development - a user ID to inject into the request + -host string + the host of the reading list server (default "localhost:8081") + -insecure + use an insecure connection + -limit int + limit for the number of links to return when listing links (default 20) + -mode string + (list|update) (default "list") + -url string + the URL to add or delete +``` diff --git a/examples/servers/reading-list/cmd/cli/main.go b/examples/servers/reading-list/cmd/cli/main.go new file mode 100644 index 000000000..b030a8e38 --- /dev/null +++ b/examples/servers/reading-list/cmd/cli/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "os" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/oauth" + "google.golang.org/grpc/metadata" + + readinglist "github.com/NYTimes/gizmo/examples/servers/reading-list" +) + +var ( + host = flag.String("host", "localhost:8081", "the host of the reading list server") + insecure = flag.Bool("insecure", false, "use an insecure connection") + + mode = flag.String("mode", "list", "(list|update)") + + // list + limit = flag.Int("limit", 20, "limit for the number of links to return when listing links") + + // update + article = flag.String("url", "", "the URL to add or delete") + delete = flag.Bool("delete", false, "delete this URL from the list (requires -mode update)") + + creds = flag.String("creds", "", "the path of the service account credentials file. if empty, uses Google Application Default Credentials.") + fakeID = flag.String("fakeID", "", "for local development - a user ID to inject into the request") +) + +func main() { + flag.Parse() + + ctx := context.Background() + + var copts []grpc.CallOption + if *creds != "" { + cs, err := oauth.NewJWTAccessFromFile(*creds) + if err != nil { + exitf("%v", err) + } + copts = append(copts, grpc.PerRPCCredentials(cs)) + } + + // add a fake user to the metadata for local dev + if *fakeID != "" { + b, err := json.Marshal(map[string]string{"id": *fakeID}) + if err != nil { + exitf("%v", err) + } + ctx = metadata.NewOutgoingContext(ctx, metadata.New( + map[string]string{ + "x-endpoint-api-userinfo": base64.StdEncoding.EncodeToString(b), + })) + } + + var dopts []grpc.DialOption + if *insecure { + dopts = append(dopts, grpc.WithInsecure()) + } else { + cs, err := oauth.NewApplicationDefault(ctx) + if err != nil { + exitf("%v", err) + } + copts = append(copts, grpc.PerRPCCredentials(cs)) + + } + + conn, err := grpc.Dial(*host, dopts...) + if err != nil { + exitf("%v", err) + } + + c := readinglist.NewReadingListServiceClient(conn) + + switch *mode { + case "list": + l, err := c.GetListLimit(ctx, &readinglist.GetListLimitRequest{Limit: int32(*limit)}, + copts...) + if err != nil { + exitf("unable to get links: %s", err.Error()) + } + fmt.Printf("successful request with %d links returned\n", len(l.Links)) + for _, lk := range l.Links { + fmt.Println("* " + lk.Url) + } + case "update": + aurl := *article + if len(aurl) == 0 { + exitf("missing -url flag") + } + fmt.Println("saving URL:", aurl) + m, err := c.PutLink(ctx, &readinglist.PutLinkRequest{ + Request: &readinglist.LinkRequest{ + Link: &readinglist.Link{Url: aurl}, + Delete: *delete}}, + copts...) + if err != nil { + exitf("unable to update link: %v", err) + } + fmt.Println(m.Message) + default: + fmt.Println("INVALID MODE. Please choose 'update' or 'list'") + } +} + +func exitf(format string, v ...interface{}) { + fmt.Fprintf(os.Stderr, format, v...) + fmt.Fprintln(os.Stderr) + os.Exit(2) +} diff --git a/examples/servers/reading-list/db.go b/examples/servers/reading-list/db.go new file mode 100644 index 000000000..93ec086bf --- /dev/null +++ b/examples/servers/reading-list/db.go @@ -0,0 +1,102 @@ +package readinglist + +import ( + "context" + "os" + "strings" + + "cloud.google.com/go/datastore" + + "github.com/NYTimes/gizmo/server/kit" + "github.com/pkg/errors" +) + +type DB interface { + GetLinks(ctx context.Context, userID string, limit int) ([]string, error) + PutLink(ctx context.Context, userID string, url string) error + DeleteLink(ctx context.Context, userID string, url string) error +} + +type Datastore struct { + client *datastore.Client +} + +const LinkKind = "Link" + +func NewDB() (*Datastore, error) { + ctx := context.Background() + pid := os.Getenv("GCP_PROJECT_ID") + + ds, err := datastore.NewClient(ctx, pid) + if err != nil { + return nil, errors.Wrap(err, "unable to init datastore client") + } + + return &Datastore{ + client: ds, + }, nil +} + +type linkData struct { + UserID string + URL string `datastore:",noindex"` +} + +func newKey(ctx context.Context, userID, url string) *datastore.Key { + skey := strings.TrimPrefix(url, "https://www.nytimes.com/") + return datastore.NameKey(LinkKind, reverse(userID)+"-"+skey, nil) +} + +func (d *Datastore) GetLinks(ctx context.Context, userID string, limit int) ([]string, error) { + var datas []*linkData + q := datastore.NewQuery(LinkKind).Filter("UserID =", userID).Limit(limit) + _, err := d.client.GetAll(ctx, q, &datas) + links := make([]string, len(datas)) + for i, d := range datas { + links[i] = d.URL + } + return links, errors.WithMessage(err, "unable to query links") +} + +func (d *Datastore) DeleteLink(ctx context.Context, userID string, url string) error { + err := d.client.Delete(ctx, newKey(ctx, userID, url)) + return errors.Wrap(err, "unable to delete url") +} + +func (d *Datastore) PutLink(ctx context.Context, userID string, url string) error { + key := newKey(ctx, userID, url) + + // run in transaction to avoid any dupes + _, err := d.client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var existing linkData + err := tx.Get(key, &existing) + if err != nil && err != datastore.ErrNoSuchEntity { + return errors.Wrap(err, "unable to check if link already exists") + } + // link already exists, just return + if err != datastore.ErrNoSuchEntity { + return nil + } + + kit.LogMsg(ctx, userID+"---"+url) + + // put new link + _, err = tx.Put(key, &linkData{ + UserID: userID, + URL: url, + }) + return err + }) + return errors.Wrap(err, "unable to put link") +} + +// Using this to turn keys 12345, 12346 into 54321, 64321 which are easier for +// Datastore/BigTable to shard. +func reverse(id string) string { + runes := []rune(id) + n := len(runes) + for i := 0; i < n/2; i++ { + runes[i], runes[n-1-i] = runes[n-1-i], runes[i] + } + return string(runes) +} diff --git a/examples/servers/reading-list/gen-proto.sh b/examples/servers/reading-list/gen-proto.sh new file mode 100755 index 000000000..dd8625af3 --- /dev/null +++ b/examples/servers/reading-list/gen-proto.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +go get -u github.com/NYTimes/openapi2proto/cmd/openapi2proto + +openapi2proto -spec service.yaml -options > service.proto; + +# for our code +protoc --go_out=plugins=grpc:. service.proto; + +# for Cloud Endpoints +protoc --include_imports --include_source_info service.proto --descriptor_set_out service.pb diff --git a/examples/servers/reading-list/get.go b/examples/servers/reading-list/get.go new file mode 100644 index 000000000..802df66e7 --- /dev/null +++ b/examples/servers/reading-list/get.go @@ -0,0 +1,58 @@ +package readinglist + +import ( + "context" + "net/http" + "strconv" + + ocontext "golang.org/x/net/context" + + "github.com/NYTimes/gizmo/server/kit" + "github.com/pkg/errors" +) + +// gRPC stub +func (s service) GetListLimit(ctx ocontext.Context, r *GetListLimitRequest) (*Links, error) { + res, err := s.getLinks(ctx, r) + if err != nil { + return nil, err + } + return res.(*Links), nil +} + +// go-kit endpoint.Endpoint with core business logic +func (s service) getLinks(ctx context.Context, req interface{}) (interface{}, error) { + r := req.(*GetListLimitRequest) + + // set request defaults + if r.Limit == 0 { + r.Limit = 50 + } + + // get data from the service-injected DB interface + links, err := s.db.GetLinks(ctx, getUser(ctx), int(r.Limit)) + if err != nil { + kit.LogErrorMsgWithFields(ctx, err, "error getting links from DB") + return nil, kit.NewJSONStatusResponse( + &Message{"server error"}, + http.StatusInternalServerError) + } + lks := make([]*Link, len(links)) + for i, l := range links { + lks[i] = &Link{Url: l} + } + return &Links{Links: lks}, errors.Wrap(err, "unable to get links") +} + +// request decoder can be used for proto and JSON since there is no body +func decodeGetRequest(ctx context.Context, r *http.Request) (interface{}, error) { + limit, err := strconv.ParseInt(kit.Vars(r)["limit"], 10, 64) + if err != nil { + return nil, kit.NewJSONStatusResponse( + &Message{"bad request"}, + http.StatusBadRequest) + } + return &GetListLimitRequest{ + Limit: int32(limit), + }, nil +} diff --git a/examples/servers/reading-list/http_client.go b/examples/servers/reading-list/http_client.go new file mode 100644 index 000000000..64a97156c --- /dev/null +++ b/examples/servers/reading-list/http_client.go @@ -0,0 +1,127 @@ +package readinglist + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/sd" + "github.com/go-kit/kit/sd/lb" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/pkg/errors" +) + +type Client struct { + key string + l log.Logger + + put endpoint.Endpoint + get endpoint.Endpoint +} + +func NewClient(host string, l log.Logger, opts ...httptransport.ClientOption) *Client { + return &Client{ + put: retryEndpoint(httptransport.NewClient( + http.MethodPut, + mustParseURL(host, "/link"), + encodePut, + decodePutResp, + opts..., + ).Endpoint(), l), + get: retryEndpoint(httptransport.NewClient( + http.MethodGet, + mustParseURL(host, "/list"), + encodeGet, + decodeGetResp, + opts..., + ).Endpoint(), l), + } +} + +func (c Client) GetLinks(ctx context.Context, limit int) (*Links, error) { + out, err := c.get(ctx, &GetListLimitRequest{Limit: int32(limit)}) + if out != nil { + return out.(*Links), err + } + return nil, err +} + +func (c Client) PutLink(ctx context.Context, url string, delete bool) (*Message, error) { + out, err := c.put(ctx, &PutLinkRequest{ + Request: &LinkRequest{ + Link: &Link{Url: url}, + Delete: delete, + }, + }) + if out != nil { + return out.(*Message), err + } + return nil, err +} + +func encodePut(ctx context.Context, r *http.Request, req interface{}) error { + pr := req.(*PutLinkRequest) + return httptransport.EncodeJSONRequest(ctx, r, pr.Request) +} + +func encodeGet(ctx context.Context, r *http.Request, req interface{}) error { + gr := req.(*GetListLimitRequest) + r.URL.Path += strconv.FormatInt(int64(gr.Limit), 10) + return nil +} + +func decodeGetResp(ctx context.Context, r *http.Response) (interface{}, error) { + switch r.StatusCode { + case http.StatusOK: + var res Links + err := json.NewDecoder(r.Body).Decode(&res) + return &res, errors.Wrap(err, "unable to decode response") + default: + var res Message + err := json.NewDecoder(r.Body).Decode(&res) + if err != nil { + errors.Wrap(err, "unable to decode response") + } + return nil, errors.New(res.Message) + } +} + +func decodePutResp(ctx context.Context, r *http.Response) (interface{}, error) { + var res Message + err := json.NewDecoder(r.Body).Decode(&res) + if err != nil { + return nil, errors.Wrap(err, "unable to decode response") + } + + switch r.StatusCode { + case http.StatusOK: + return &res, nil + default: + return nil, errors.New(res.Message) + } +} + +func mustParseURL(host, path string) *url.URL { + r, err := url.Parse(host + path) + if err != nil { + panic("invalid url: " + err.Error()) + } + return r +} + +func retryEndpoint(e endpoint.Endpoint, l log.Logger) endpoint.Endpoint { + bl := sd.NewEndpointer( + sd.FixedInstancer{"1"}, + sd.Factory(func(_ string) (endpoint.Endpoint, io.Closer, error) { + return e, nil, nil + }), + l, + ) + return lb.Retry(3, 2*time.Second, lb.NewRoundRobin(bl)) +} diff --git a/examples/servers/reading-list/put.go b/examples/servers/reading-list/put.go new file mode 100644 index 000000000..25eb61361 --- /dev/null +++ b/examples/servers/reading-list/put.go @@ -0,0 +1,82 @@ +package readinglist + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "strings" + + ocontext "golang.org/x/net/context" + + "github.com/NYTimes/gizmo/server/kit" + "github.com/golang/protobuf/proto" +) + +// gRPC stub +func (s service) PutLink(ctx ocontext.Context, r *PutLinkRequest) (*Message, error) { + res, err := s.putLink(ctx, r) + if err != nil { + return nil, err + } + return res.(*Message), nil +} + +// go-kit endpoint.Endpoint with core business logic +func (s service) putLink(ctx context.Context, req interface{}) (interface{}, error) { + r := req.(*PutLinkRequest) + + // validate the request + if !strings.HasPrefix(r.Request.Link.Url, "https://www.nytimes.com/") { + return nil, kit.NewJSONStatusResponse( + &Message{"only https://www.nytimes.com URLs accepted"}, + http.StatusBadRequest) + } + + var err error + // call the service-injected DB interface + if r.Request.Delete { + err = s.db.DeleteLink(ctx, getUser(ctx), r.Request.Link.Url) + } else { + err = s.db.PutLink(ctx, getUser(ctx), r.Request.Link.Url) + } + if err != nil { + return nil, kit.NewJSONStatusResponse( + &Message{"problems updating link"}, + http.StatusInternalServerError) + } + + return &Message{Message: "success"}, nil +} + +// JSON request decoder +func decodePutRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var lr LinkRequest + err := json.NewDecoder(r.Body).Decode(&lr) + if err != nil || lr.Link == nil { + return nil, kit.NewJSONStatusResponse( + &Message{Message: "bad request"}, + http.StatusBadRequest) + } + return &PutLinkRequest{ + Request: &lr}, nil +} + +// Protobuf request decoder +func decodePutProtoRequest(ctx context.Context, r *http.Request) (interface{}, error) { + b, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, kit.NewJSONStatusResponse( + &Message{Message: "unable to read request"}, + http.StatusBadRequest) + } + var lr LinkRequest + err = proto.Unmarshal(b, &lr) + if err != nil { + return nil, kit.NewJSONStatusResponse( + &Message{Message: "bad request"}, + http.StatusBadRequest) + } + return &PutLinkRequest{ + Request: &lr}, nil +} diff --git a/examples/servers/reading-list/server/Dockerfile b/examples/servers/reading-list/server/Dockerfile new file mode 100644 index 000000000..0b5957343 --- /dev/null +++ b/examples/servers/reading-list/server/Dockerfile @@ -0,0 +1,7 @@ +FROM scratch + +ADD ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +ADD server /server + +ENTRYPOINT ["/server"] diff --git a/examples/servers/reading-list/server/main.go b/examples/servers/reading-list/server/main.go new file mode 100644 index 000000000..aca6c7588 --- /dev/null +++ b/examples/servers/reading-list/server/main.go @@ -0,0 +1,19 @@ +package main + +import ( + readinglist "github.com/NYTimes/gizmo/examples/servers/reading-list" + "github.com/NYTimes/gizmo/server/kit" +) + +// a tiny main package that simply initializes and initiates the server. +func main() { + db, err := readinglist.NewDB() + if err != nil { + panic(err) + } + svc, err := readinglist.NewService(db) + if err != nil { + panic(err) + } + kit.Run(svc) +} diff --git a/examples/servers/reading-list/server/run_local.sh b/examples/servers/reading-list/server/run_local.sh new file mode 100755 index 000000000..bd29804bd --- /dev/null +++ b/examples/servers/reading-list/server/run_local.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +export GCP_PROJECT_ID=local +export DATASTORE_EMULATOR_HOST=localhost:8082 + +gcloud beta emulators datastore start --host-port=localhost:8082 & + +go run main.go diff --git a/examples/servers/reading-list/service.go b/examples/servers/reading-list/service.go new file mode 100644 index 000000000..d74a89f56 --- /dev/null +++ b/examples/servers/reading-list/service.go @@ -0,0 +1,149 @@ +package readinglist + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "os" + "time" + + "cloud.google.com/go/trace" + + "github.com/NYTimes/gizmo/server" + "github.com/NYTimes/gizmo/server/kit" + "github.com/go-kit/kit/endpoint" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/pkg/errors" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type service struct { + db DB + tracer *trace.Client +} + +// ensure we implement the gRPC service +var _ ReadingListServiceServer = &service{} + +func NewService(db DB) (kit.Service, error) { + ctx := context.Background() + pid := os.Getenv("GCP_PROJECT_ID") + + tracer, err := trace.NewClient(ctx, pid) + if err != nil { + return nil, errors.Wrap(err, "unable to init trace client") + } + + return &service{ + db: db, + tracer: tracer, + }, nil +} + +func (s *service) HTTPOptions() []httptransport.ServerOption { + return []httptransport.ServerOption{} +} + +// override the default gorilla router and select the stdlib +func (s *service) HTTPRouterOptions() []kit.RouterOption { + return []kit.RouterOption{ + kit.RouterSelect("stdlib"), + } +} + +// in this example, we're tossing a simple CORS middleware in the mix +func (s *service) HTTPMiddleware(h http.Handler) http.Handler { + return server.CORSHandler(h, "") +} + +// the go-kit middleware is used for checking user authentication and +// injecting the current user into the request context. +func (s *service) Middleware(ep endpoint.Endpoint) endpoint.Endpoint { + return endpoint.Endpoint(func(ctx context.Context, r interface{}) (interface{}, error) { + start := time.Now() + defer func() { + kit.LogMsgWithFields(ctx, + fmt.Sprintf("complete in %0.8f seconds", time.Since(start).Seconds())) + }() + + usr, err := getUserFromMD(ctx) + if usr == "" || err != nil { + kit.LogErrorMsgWithFields(ctx, err, "unable to get user auth") + // reject if user is not logged in + return nil, kit.NewJSONStatusResponse( + &Message{"please provide a valid oauth token"}, + http.StatusUnauthorized, + ) + } + // add the user to the request context and continue + return ep(addUser(ctx, usr), r) + }) +} + +// declare the endpoints for the HTTP server +func (s *service) HTTPEndpoints() map[string]map[string]kit.HTTPEndpoint { + return map[string]map[string]kit.HTTPEndpoint{ + "/link": { + "PUT": { + Endpoint: s.putLink, + Decoder: decodePutRequest, + }, + }, + "/list/{limit:[0-9]+}": { + "GET": { + Endpoint: s.getLinks, + Decoder: decodeGetRequest, + }, + }, + } +} + +func (s *service) RPCMiddleware() grpc.UnaryServerInterceptor { + // TODO: add the cloud tracing client + return nil +} + +func (s *service) RPCServiceDesc() *grpc.ServiceDesc { + return &_ReadingListService_serviceDesc +} + +func (s *service) RPCOptions() []grpc.ServerOption { + return nil +} + +const userKey = "oauth-user" + +func getUserFromMD(ctx context.Context) (string, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "", errors.New("no request metadata") + } + + infos, ok := md["x-endpoint-api-userinfo"] + if !ok || len(infos) == 0 { + return "", errors.New("no user info") + } + + js, err := base64.StdEncoding.DecodeString(infos[0]) + if err != nil { + return "", errors.Wrap(err, "invalid user info") + } + + usr := struct { + ID string `json:"id"` + }{} + err = json.Unmarshal(js, &usr) + return usr.ID, errors.Wrap(err, "unable to decode user info") +} + +func addUser(ctx context.Context, usr string) context.Context { + return context.WithValue(ctx, userKey, usr) +} + +func getUser(ctx context.Context) string { + return ctx.Value(userKey).(string) +} diff --git a/examples/servers/reading-list/service.pb b/examples/servers/reading-list/service.pb new file mode 100644 index 0000000000000000000000000000000000000000..f84f2c1f8efdd290dcda849480a88e91495710a3 GIT binary patch literal 60807 zcmd_T3wT}Eao>xx&jAQ*@BtoD6b0(B1w9B-ASh9?WK)(Mkc1@2d=&sK%Z^PQ00$r> z5C_2lMA5d|)afH>Q#*0o$d7!EA5oJw?ib%Qj=wLp<0h>eySaX}Nps^Sv3>I;ikj9} zjgr<*lQ#GFpIK|~jR$QxwbJ{27oRPjwfEYyX3d(JHEY(aS?Mo7l3q2}YRxS)4>y+P z4_{bWSsGqiZmqQHsXrd(@xYO^_8@QS2h+~w)rDqzZzZb^_8!?iT+|s>g>hbvdzFEY zC24njR_{-{+s%dM^h#@)Z!3d6<9WGWPpfmymA#1`KiDy@f}Sm{uI#PGXB6tY)7nz2 zy|T9!9$KlsH|?4M@=9}WXLugU^*w3lQe$QM!rrd%!i)8L(yr;%_DbvG-fmv>9=WdE zi{ti2c%N;otTdMw!6;M*soGR)=JMVxf;Voz>!aze#>~w8%6x0Fv2cEBesN}gajw0$ z_nXkCUVLyY)IZjfZUI-dH*jyda|04;`f=(8+SzYC` zoE>}UFDBD1_0H`bA4)3eCu?bCYrVR?qkiOPYuWME(&gp(xeF`Vk=yUMJA1(RD;ry! z9!}HjRu^ZQ%h}3>W;U|anC74GW+;1YbGc2!*^%Mfvq4oD2(JbX+?!^X zTdUc{#^ntBvekAI2=nc1c7CCmHJ_YrF0Ewqiy1^%T9|JvPB*hl^D7sul>lQn%^nF5 zT2m_xDmSRUbUACymaAor6_C~61(@OJ;lr0MT|yRwsL^e~yU{*;a_so%>50)pAj=2m z77>qjw%q)l)%j&QIdwT}EP={&V+sTp8ke%xa@LqzZt`xWB~&jh!##_0Ls`2uyKNxRcZQm^)P*Y&HrUafcBkoKmv?myVl;f1YfrwUZ6Z@D_%mUdZ5FDY(Mw|KGQ zCam zqSBWB05K6n6&)f)AgUZHgX8Y&ZVV8w>Npr6@>m70R0ZNyTlNKrRY6qIAyNdQ%Aqni zUUmI}0C7*p{s6I7ud3jcYCznxWmkY$6GRmqB1Iso94Z5G&z@TY#H;gOch;*ac%@Dt zUcF^+fY>RBDmp}pKvX$Y2IAHGZcIN@Nt0Sfy>?y4K&AhmSF#h$+4;q0+t};D$;q?G z<>Ku8-0HIFA>+J8)^098f$R-uV=H+?xIsjoBKYKj8*j%~Lf^!kl)?!m4aj~`B%nYsZ(TT|n6&NbDQEO>ht*MjO zb#F~SR7q>K4w2x-nhP`QIanRcR+^}7I_^r> zSvZs75W2Fq(lo5T+?>@U+;X68O-Lfcda@3wo8eva$p)=7v#F-|>(YgJ#A&A4o@T~k zlo~#i4~$NxS&MnVMOTv)K-;a=Wv1k5SQA@NQ#>6CBiBBP5ZQzqBs@E1Ln^U+i@(US z<)!KDf#!<8X$}@Y$DI!j(8$W_G8$np)HzUmoPm%vI=F7E4h&;+X7^OyU=Do`@&HMB> zHUv(0tZyo>RG??ovcrc>jk#f=K|D8pGNM^(4cOE_a<4kTCy!FjJNBrIi`C_YLlWL5 z+E0J$`nCb+nMy;Y*+Ua&PD^Z|z;HI|5{Ur0{(7q(r`aL>z199JVvSC& zMe>d#ci#1~yARK{T90PgTd7dURl!{v%!(&hD4eYp>&Q z(`TWIAZbZ3g#o?XTxy#B$N0=HvZTu{G@d{!W=qR5MB%G!aIv*`DD?Ay6nj(5GP0Os zyH*oMEfw^gW*KX&#VSTdzT{2Vq!Hs{C~dx&YO*AuRo6g`g#$iPHX-SoI3o>bBklGo zH3iV8a4Ccmx8x#a6JNwDSDv9u|KC$V^NTBY-^Iv0F>hWJ{gNSU4ctOm_?Hl76~fE0mjSaK@uQqNYiu>p0zKB!+cDORAsbW z1X4sAJAkF4WM5o6-C$8}hDr`eUkn8q37j4Tza*C=`4u)+t#mfiO|O@qw8v8;l%b>^%sQURImNBby0& zt`25~V4EhaY{Lq}GNKqR!D@Bu#Q?0E4%Do9V&eKlvw-9YM_b!xXJ)gOyn)|bnViK* zvu133y3O8_4alMzU^E__zi}XY>QT+nYsfrU(34tmnhp;Epzrg~ZFZi7#^$%dq>ycs zJ$mrbI0NHKlX+!QYQ2j#2Rg*pb-cnUmNoJ6gjsU)Ij~rUv@f()S*kF&mUJRyB>zggQeYYY^LG3gyxm%0-qXXPDCmw4}e(s=MZY;K0+1qN8 z39O-66Nec@DG?;f^~KeNm3b}cOLHnNbtYN)jkL(Z!ZnIJ0Uj-OF!C*rN5kTJ&1&RD z2xDp0QT+VQPJp&Pdyy01COs>rF;gzS+%s^D?{fR_Tl9}P34-e7QTU~&CF#!B(gO6Y zX4DPTDDZeuLLacQiF3^ac|}#ba;XIae-sULQgcN7CzyW3^v#jt5%?NjgbO%$fz7E!C$rue=F_M0>#tpnJEzMau zkNor3Vik*j6PtTB{x+r8u*IXzq1ZI00)3!NXQk|@Wha}B*{JIV@i45i*T#An7YiyQ zxa4rR%6l65Ip|nxIrQ&9G#jSzFSpqTGUEYLVdVmQTfRgHK9<48f$WqvVhj5MJ1;j9 z*kaP(;M4(ZZ%hknp>2$`gY&cggN{HDT=z0P(6(sLu-7NxJ_5Ocnq|!eRv@Li#ZF@} zVh2LA(+A9x-eT;Q10*oIb6B?M27fWbsP%rBa*RLXc&}Y_4FcBD0@z(F>hUXB+cGYF zOA>F-8US*9$!(?`N%NYBA`wP#m|f=elhLVW&X3&V3yIdUnTR?W{lW5W4rCL}xr?;l z&fcIc)~W1`Ss+^e;=Wi%_Gj@$_SCwX8ER$V-~cA+z(M=x;)KOp` z9mpaQ_Xd$}JG~@JIMrZ4`50t4Fx46L+D;JLO9~QdR%N?+v9ZXEEr&ZhO5^t*&+dNN zJ-5GcP>#elo53rs7GC=K=E|&X247gYxNvxRc3PDVKtlIz-#L6__((RPt<+{VVV>O- zUtN$h&B)RNIg%wT-`reg+bL{0l`&;|gjsx3RB^Y@{x6|=XEx5-XZZ=a;0i1=+i#LV zWsU<(d1*P@R0`CK;$V0Nu~Z!m&iYP<5h})GVV?Ph)rfhIgIr1S1Z@4sk&}wA)eH>H zPP>mM5)ihfU5m`a>rp~rN3+v{Zi|n-TQd5h@Xf9+19v%lA~3ROu5FiC)-?D{m`QrL zXqCF=!43)=6*fFKoIUy$fIY=rr@5F&fhQUZ(hI0hdvo8xr_9aaq*o_R4_F_~B8;fO z&xJY)6N(L!`V+TyN-h>xC&pQgrIKLLt8N5`WuRt5?Hn$**iyxJqj3(@ez-Z64L&$| z>Lk+{;SQ)qc&?YN+)AZhd!f?Zm;RvkaupU(`)`$GPyb&>xV2Y+8_fs->{hVHjk$!G z2YfVb6K_0jue~AeF|65@KGB-S*BjgovQCEcYG!^1-B9cb?qJszY-;r`(0QR!XTRMZ zbQ*r$u?Y76ta7ahdaFljy_GMgFFX}V$n2P{S>iB6 z%!OzgcRLU%WQ#qax8E!7civ^NtcTrfRoDKk(!C=cv(cjTKPt6d{r6@tHk-h3oWcWS zHb`DT$$rl-@`fJD=xI!~*qw;(JdK^xt^#F&l@|?k@Ami>%Knea&U$*t+D+=U?@eg; zhN4~bPguh_@EmpmZ0bkY39!F6$zdnJ{@x^qP3ilR+U_#!>18%Kt^m6VZ0bkYRbYQ# z(z_$zT{UR+T>uV6t^u3+5jN2P6ni7L)qwqX61dH2Lfs!oYFCwE ze>0kNdi@By6WBkH^!5e3JAwTJ$*$e$h|#;NUi-nMc4PmYL3!mY>3YCNK3{JK9>av3 zD#o|VVO$j&=%T$JO!izGi?sKH$qiZh{=^#GQm_38@jdv<1Zkflw`h zfLj<&9z?~o1@it#(z7kTg}gtK^zBUlxog?(dhOjwa;X0!gv{hYm4RF#Qqm|Tq@tpv z!@gLFv6nWXr{?>KGdkL2bC-qp9dH)`_XOr4(S|m$5kaR$WGhGrG>Mx{4(a3)acayo zn@b0BY3@dfz&>(rH;nb}gkT!iBi;1;-O29#u}II~og5rWA0V10sdxQYqA<$-R}`u+ zj_@~QWt%m{&mf^$OB&?IlJ09#;&p3^JgdE5+8eDq_9!p-af zBgg|EK?`4_7)FpH{dm7d@MYE_?SD`$?za|=Q`gTRB`E}JZ?rN0oxo#{V)E=h?#v?ykKG_^W=*h5m5h&T=b%{xNWN31b$D&2WM zVWTH3c10B@h&+CIT2m}(h;Y6+hSJ85hSz>^R{s8R`(ohtYU!>`-YQO4iXz7D>Nio# z#QR!|K?vWC#rpMWI>QDWv9XKOmkEHb5^7v}Qok+TxwJY(K~W)r;p1 zLZ?C4NWW2PtcTLA&jN@o!D=IFTPW<`fWm~w|E-4+zpEbyizhMg zLmj8y7}dtN`-ibi_u@3W1S?u1l)6^Rb_HEF>zgbH}ABk_D zh$CsQFSeW`#LF*+r&9lDBTG3`e!DF7x^zd*59e`<&*`LqFgd(oMAngYcr^0SxIP=- zp2M+n{Y08F8MI~*ujvK&WOFi~QI&O&$9wF<6MR>HMZs!1SkQZ9Bd475K(P(nb}rqn z?Mf}#L(h5)iQ#YAv$)dtj%|xjyk9q^`2&l9+AHm2t&y)Uhz%uLI5j_YUtJ?XYJ&A-?W2j1P4?%qsrH)Vgs2c#s^{LB&-Dw;wt9;vk5f$L+iNzO+Z*J8kW7 z%6PXPh^{&*V{C?T5vGQ`TxVdgW*h9*oHx@loJ83g9==xQW}U!!=SnQ5=98v((+ z$Mi(aKh|z7#-X*tIC@~s-&1hoy3;YG84%sMo)-sx9t&B0tnWyBCm%UGdj7Kr?B<|M}mA`%mm<71~E=E1X7(IRNRNwYG>m6%x;(nn;jUDSAfoWe3Wx}ECT=Re$^?~DQr*Rity#3_J zvC)&~&z#i}jggbkE`K&Y`q$2ljgOvyex+w;M@J@kRvnm5cWiU&Ni`Gr9=YGu~rQO!lRFR#)=(!UgNq zA6bQ$7ev)DzTP9(U!kY#@vPsT_PLAhJiDLG1|F+9JMq_jv~!lA(&qRMe~q8r-|7pS z8<$I8NZS@)`0M=BQO0S)6&0O7)}Z9)ki6A>etoM5Z4< zzq&H}^1b`Y?Hh*Bg*wMo3U83NyD5(!lVdWm} zoH#o=dSbjcLb<=ST!*3NS~-DxK>~)FYen)xtm*0V)@N2c8ZW{q+I#!T$nKn;w$9E^ z2Oe)D4_6h$Du1}M{Gs)7{in3Jba`Wo_40>1moBgW>=s+Lv7M^0JtEHByA-e!T>+hXpLWwQ~Lv8%bhqd=@6_xu)11YT+Pwm^v^nRq7|x8x~tA7etU92vz|) z10z4e`NlL{+D1mCA#*6#F198@1yRQeD_VlF#zgtbWn!)S?)|PoaEnC*;xBbXX8`OKb_V95{3P-!#N*nht zweXd11Tiw|jfYbGfwb$fw!xMb-+p8xU#)@t(1Zc+d1&JN=`-V}Mox~0&+FHuwS~r; zFRz_G?9q!I1R%(PvQ}x^qnB<71`em4Mz4CB28#7{)VtH#@iXHm5C|l|>3II^*ywR& zV&G+ImlHw4l9Pdtycld_6D7Spck0;acwe%1sB3+l18t;Gi`Y2Ee-&Id@GmN9Z^^*Z zvc@92&o?;N-43jg+M^LYd9g$HSAjgj(b?BE@cv5Lmz$!Icx%O6xezuL^S=r-2Y#%Q zZVz+j=IGt{zZJc&Ch0aeO}|a-z9-$uPVdDfjz{1`J#XIny#uB+HadOQv@kqYe0Ne? z9!GZ(esbz8XKqg)f8_kR)2}&w=Hb)heI@W#?IjfZvuWRYr0Q3vo1ukd?@D)^KEs^I z#5j8Y{iDYxCwu{(cY+0YNx%o*TTORuG8?1D-DmBqw?iAW_$J>B%epv=|7YAjl{EsD z2+o+D$1?WCpf3n^6wlpdF_hNX9=3B?xJ;vSLkol&PVMk!Y;h&8z1W;%=kp0USYvHv z|EP}l#W#6vES;Ga4)bO}L32lCyw{&OKp8`B4?C7}!)+b!^=DoMM|xw9tPM090m(hm zGrm24Y!&+-O1mQ+V#nHfui_J{jtd=U?`JzLIvw$4s}LMnvE`k0pH-PbH$~*ouoF zmOocn<4W9y4mRN9zG(4eo~LDeM|gfbJl0;t$};Df*VMK2E#rOhMPB)6dQF6@cn5_G z77fS+-7|i5s6fnuy^r4x{M$;p(=2jX-?<0a8Uc9&;B2Y(!K2O_{FYD`cHOPx|jW=d-G1-!@yVA*2WjMu-c>528? z3=dgOOyoE!phz>t%P9Ibh7^&>MZ3n@FfPumu=ng~fr!ykPM3=u5(Xvx9bAW)9qSJm zY1_y{(pX9<6jV?NP9aW8NotHLwz@L4N5d7x7n<)oDX-E!3e~-t7Q<_ARE)ZR1xw9u8VRmg%fW2XE=gavr^8oe{R6RhVUVU}>G1^0%CJqu?dzJe_a zo&soDNL;*x*yDvQi>e5!L!K+$0o9g zGxtxDuV<7*KH1svGp}XRK9L=JgqNda?m7F&_}BvvPG%3DIeB7qe1hyfCzQ_z2luga zlV`>!(mV%{-YB=v=5i;jGa1ratvsI59xr0va!?0Po6u0dweJZg5pL<&NOz4 zRrHB$^30GmyYV|E4mveDe*8hQ3XL2aW6}MHp>qG&7@IhLa)br?34$t5(`GjMT1EFxJUDXl#4$JT%d#|FXZ`1m>V{c}3}%)?M-JUc$Zw;Ik983+CuRq@K` znej&iR&=lt7|I@gaFplcBC0XPhzOz;-tozDH5!Ch80ey(+3C>-PL4f*eLE_A&IruI zV-usq#gC7H+XI3rc6xY(Mqv~h7Ds{?L!{p&)`iH7XEsK1sS~dS2v~b@wZvWmW4KAJs_#n@W>uCQBxV$TL&V~?`e;$vwjYX4Y6L4D}+7TOTy zy|8ftVrrY8L!Iel=tN`jF~WBqAjqe2sj)JYJ=C0?CG4@Wh&o44nx<)+5TY}JqbDRg zLA%O3i=4}~L-3E(iU*X*1k?q%rc8VCg_|@IcAg`7}iC{n?bGAXy#!8{X|r7u#^GdOuRAeBR9GB_RFJsBY0)-e(X1TRgbcmi?OXW}*h==;`4iJYsZVV7fqNIXX zQf{r`JQJ4X)>6?SdTuS1LuDWi@5=(jBOTYKf0Ix+l9`cQi&FneV7(%Y$>(Lop#{gN zM>!HQ4MULyp&<(ge{itd4oyi~bVz>=aap-=kx9!A4qj?4Kc3C3+5y3-R%?aTjvsr1 zV;nc7^g5CcpQZFt(L3TPy;KgBq3V%6`_jiequ1RXFR%2!$)mu;$g&O*<|SBs*#yD& zgf?(J4p!MQvV6qJ5;qqivV!m}Vo4x(gXvfT8*^^mO~f0?;ymTnJ;|U@3c0QBS(Dr9 zp5E)@I~etz8*YjvkiX~F{pn##ZB@Is<8_t(n@|xh^g1*m+?8aqEYJZ1g43$^cJE7H zVG*Km2jSRv-ikXklLF!$K3YI^o9i|XdvyaWfllAv1k#bxD{ ztT{pnd|!D@e|pfGr2YGn>j$p$qXm|WWyn&p3{N$d!KAsOyo>%Nk#7!Ff2;yk553FUR-m``4^B3dfSc{u7vN1T46nCRXtHHsvNnx6x?8A()YQ*qd9#W;Exh zR6*rfjtbGy)nh~zdshjLt{&TInd~4nvC-9Ix9m^fY^dEU>*D-eo*Qd{4_7DcJ($y|lVorY_FAGss6rri^}&6XKTsvwd+^|G zCUupBiet&4fJzrY#&RHpS69b^)KL*2V>h^bQUb`>Z8mcN5=Hn>G8}*qM@%X9d<#Gx z>NWkUiU4_N;6SVikcWm21x&~s`I;OE0lAbS5aMyGuj#!Zd`38K^)*=p(hZQ;UR zggEWhr6B*$;#9v>x7f zUAo`mvTJYXxLoPqw`M8`7Gg{prAbJ->O5K3~@lA=-ig0LvyeS7#fvRr`oXk7b%x($I5gJ{6)8K&s1g(8Oh(nS{ z8K2LANNCUJK-7WrK^&+Fkn=$ttk6hq3-F~z8#$0FKpH_sP!S-FeYeJ%0BIbse5R0r z&!&QkpkO6u8A*NAYf*;u&27X^#rJ&P++}u|*QcrGJ{|n%vNEq{YnEiyi-4Ogfg?h_I$Hv# zVVK>uCkL;An9WC%$`|s1kea=a(@<*mLXeTf)a-?R*GrTDav>iGDn6DR4&;@ZeJlqe zHTzgTno_fmWs!NLW*Aj}JZWxQ%|}ji+iI_?Fx~-X^%|e5DFI}asrqd;atV{$lgTap ztHr?hlwtlb##A{A{GDp4#Dm7AGl<%Q*&27$Di4tJnt3@D-k)E&hG9TH?tf#tkeD*R2F%`3nKda%8Cz!!g0 zUt9`bQ2tv&bS^uM5WRkW?)6BVWt&n)2+l+K=2#R$MDOutcE|15=qs$7G96CLJap9} zZnJsLxW|1nKeOg@>cafvm>H$c=zI|^7pSow=Rv76oXq*#<<3Y`@Sx~E>unyCI>V`{ z@BfZ<=FTH8zB4+uNXtcMSh(|`)EON`EO*8ZBUWmC`N*(Brbg)t&V2p7@)tTz$b;gG zF24AC7W~NgEV)7m{Y?)}6!IgQWgL z;SRYE?oOQgS}5mdnmLc}DZn^KM`|JzzNLo1ptDuH8Jly~uwt)l6Na4>+;Vu+5vG;Q zUxvwnBjl@#k1w_^Ee3s5;!PBXI#~>74`le^O8>Q-Lv=w*>$P`61s2LDn~QTR7dEFu zPI$~!)i1^_1>idc@TLH;w_f{)BI_<@}BaEVeQ*BBKk&<~Zr5C|R=Uw93^ zkPR&VWbK(sIGjld?MczkoKi$%JyT&Wwaz9w75_|y1x6^MoS&&+PdFrA{-a9bC!MQW zVyGA)=>XO&{A7U>w!VrH5(^EAIVAP(Co4N6PAoMj zf)k-g>K?_Pt|Zs=|AUHhZYbE#Vp*k;2+#~0o?4DZ`-G>=Xw;p;JMK$iIrZ}|@qD6J zif>~Q6OPEpXtilj2Rhj9(xH`b97JZQmF>6)tm_nH^&|B1B3=9GN;p&x?sV;^D}B3T zW$Vk;d()5W?08jV`PoX6_5W02{-C&w6t+#wWkZ<_Xj{$GUAY0wo(jug^~ie~-;lyo z$E`3$@atmw>>u7A6W@^mW8v~4MSfdmCcZnvSId{?+qff--*(#}P3>8mja(#?{2-PW zON26W+?Ly62U*Q#gY(0*Jv+ZlB4umcPh9Jrw{B*kQd8xqd0y{(L35p?}F}p06yv1)Z6vBuO- zP41kMJB%!k&gN-M&0E4mFK;9se`0ACp~Jw+@@iApLf|siISQO6R>Py8sIaD;aMH}v z_2>=b*j6s>l?-orgPif22enyssd1Sh^u}a&t^;&DukUDO7W82jYW6C!$RByR32p!i zpvf~Q&J6nQ;DMvQ33!Nn(r)&?Iue*=v1pbTq4y^XddnR9WQEm*)0>qs#etjBzvapY zTkMxAc)6dDJ~5S{WsMWq4%10vG~ildVdJ8-yjg)b5bCdePBX^|+vGPZc=H{SCSi&Zk~HjZ zR`BPCw?O*M3J(2H1k!I-@aW$GO}gq`zg_A0zoE%ZWtxN(FJW#Y&xoe-KpknD->!5! zi*<=QzZ0oL2}~$DCKRF0??mcQU_1*>oY5Ff%MhNwN6TkK>BKhP1Bp9&X#)D z?^QbfBh=ZmhC1>H5+D``BHi_Sm2PdA)M{Hqkl&94p+w^py>W_k7C|Hz)y#VuU1idA8D<#kW@ZMo+{-WgtCU!9V6WL6GMv_(vU*2tqMJ z5<#A;;2{lff%IGj7ilO0>A4C%(%T_OcfIRBS33SX1Q}RE5TXFNRm$g)wZxHr`p=c_ z>kN@@5$9`>IFvwyq9Z~P;(RRaB&Gun3+u!0#gUNI(N&N?Gh9=h|w!B zi@g%({!!5@aqb^g*dZ2qsZ6i_NHd)Vs#~1wY|DONHQLPW*rx1V;N(R*^YysO=+*`a#lDDw?A@wkD_%|L0VN4~f%8`ZUM zVKG(S3b!}`^IF>-F#~S7wAo2q@hJ1MWTe?+x;_JoKVFVma+!PVjYaUn^!U1nxz+_( z7&h&Nd9K4`&z4|fgaYnZV7mgNTdwwCL*hu5F)f#}!;l?C9@2Tg%*c<#`%;2vnBwW+Sl(+jfz<1WrcEv6R9F5F{6F=#G7BWv0cdn&h;O~H$ z+2t{{nXUe#EfW*v5*4QFOBL@3!zy^3Yh&TcH;0gTKne}=N3%LL$1i!}T<(U9kWRy} zBbdGHcK!r~MA5C)xeM7V^e3*cgre>b&=s!`og_|VzN03D)T5a^M#hJMn;x{|pQCo?rMSknP#~2~0tU4kzp{oU;r{!X)i*jF?*P6D;O=LERP|hERI5NZaeE_wndpTma6L(d;S=~qM5CT zEbdcq&P?ectJFisv!#Vq!eI|sSJr%TsX4{rD!Fl3zCHoI^1&Ynu^!7>vn~>8npv2K zk^M<75o8&Ef$RN5cEv2o9n=bP(L{*PwQ(*%CA~NHF{d2M90AI^AG$Pbkt>RSRi-5I zbfj$L6!&2nAYc>#3@uCJW2@9vPuy9k3+`Epmv}+8a8vDZmx!q0+M^C(qcld88##~o0!&NM_b1)EJm{!LR^v}3$xZ#gUx66RCF<-$ z3^~eqS{K?O^t$VYF=)$0U(8Qo;f<3ZL@bB(I_hbm!O9%@JjUeZ$%o9L=RBVS=1Yl> zTYp9Yo^YYDG#oM7OXxk$KQI@foP3@%O&GsdRI0gjIiGRSx^2EqXNxJ2=VIg;Y|j~P z$AdH7Vs!(AUSp5Y?YxLR@)I#1DQ}_CPbB2klZ8YPd*mk)R&}3}ldVU#;YX6>w*HSM zxz|TJSd@!o%DochXl`-wC56nQJmm5U>XJ!a=|22hM_-_d{c*D*E#^%fO*S2*!EKYN1%D=zMW;w~voxgEqWMm>e)|EMT8oW;kj6d6_IsB}!(RQ^cqr-Lnu7>O;XueOd7zDX1?i{v%1}@4aC*Zd2QhRGYO?i&b+AFa zo3hLV?$fpiSsOj$k0MhH;PX9H8|*dgLo0FTlnj@-ISJE5qfy_8A<*LFHTIZgMT-v} zz^<{yCN{vOCV==C$1EGGt(eX=*VFRA>FVX203L1^L`JJCMk$W4R#!h4a|!pDU*PAG zKKBbyWQ2b%xz&9N6tVYzE;;b>2nu`u=aM7$qz7#Hsr&IH870DQQbCeI$IY~m3uvo* z9>QX=C(*);x)BNGH~x5mgcfEWPx=mpX0$N-cyjxZSOkfWCwHDm`_gVizN*W@_2jPO z>CUt#K7ki`^s1En3i%_i_NsJO+FLxcwy0S8*4lgZ@$^|&D&YF5Bza~3{}q+WW^8@L z&HO9zb-o7B!Lns6I7Sb#)D8EV&183K=Uq>gZvCPzzL&|a+fkWAeAZ|{$HhbEChtG= za`FS?heuerd5dTqtE45L40$uru$4}Q8)=L411@YRXYrY=Eo$ZbY6ykZLa^MOTN@-b zSz`Xl8(-VD0%^(vF7NUp<{nEUES%6eH^*#mOKt0-fEdV{GliDQ5IQo2wuU}cFol-N zpGx|o?$J{DQ^|qb0#j(I{HbK<-dKb)Kb5@fXnIndNvu%Uzf6+PkR$z-T%{S8adAcn zM|s4r%1z^OKz;-R{bVE8=6S?ZH-p#yWx`>J6Kp#JTod)bN^08&?#hAlRXAc`pUXN4 z%PidgTh}8DZSyjG!1+TVXu)t@N z{ZTo|8S&XdImsFESt+OAbtwka7n5Y5|JRbjntDKyD`i^>30l>C0EKe?*0vNzVtyZl zc)JWV?30vFmYVG4CyUKiJ*35Yrn5b^9tduYh?yUd08w3Cgwc-{VyEklOMAg; zJ${A7g&JXPBYQ?KO6E=g!vwgvvN@FXxUBhDQY>3IpKzjNB-*fp$Z`0(7~C%w)`MK= zUrd4zutz5bDBf^m`s+301jg&jvK~HN%O*U`$VP!Z(lD>vQ@QuAnYc z%%jlOn6+ARF-8PlFu7rL<}9fVSlBAEq&8r`Tudt3#Q1Vy?q~z{%Z0hK1 MLZfUk z4n?E5=Hlg7k|fSJ+OMP-+tGgISBiGDAMll=&;8Op3ge|Xcra+aZUc4O(O9INuOxT9 zBDBM9=2r_1sohM9u^r-VDTb((oX1Y8Gr>=`V269d1z5M|787ii zoneNxmLiY4dBoAfoIg0@qFzUOrqJ`*f}RQ+d^YLxm<}ieJ)cdk^UY$4(DT_OI~>N1 zj2aY?@xNi@B%tsQljQFH@5wv|ph#*lIg&2qkZgg5o1U7?=B`Wc9$hiR-HofT!em9N zJJ{j!D2tp!3Jw5~lyIQDYn+o22C-`#l%bfjib5IxFbP`^Jqbr;D01q|7uLOW?GKY% zUlxmW?GKZ~cct%iRw2sq`6M~e|9vIb1PKh?dzDRx1v4X0g2N!tbceDz-OT%>b$RKN z@`5PukOvU(rO!VW?SKN~0S0E!A$fPV(p`{>-Q7xepD!jK#bG|5DCx2}4z|+W=Or1s zW2Pq|2J?l4LyGtMkw_*{3t!zx5r0!c? zv=C!-@biF%P~n zjYu?0Y-x?}L8edcc@GgPzEz+_>lK?3eYJMY>A4+xLX$|(?a=cZ1w9q|`i;ULv>keW zBf0S=3mEH32z&i=4eL^2@{#TG#d4>rZjdrG*9NXU;hr>6K`S=}**ll*0Cy0`i{Sp} z1?~#M{quqsc7Xezix(l7fEuof5ePKtsYn&h8>oi?3^fPd9ABsQL?Q${P@jG zPC<8{#zV7?OdrSqF(T84LHrj9N43IRkok+`hC5>sWd0(#`xWWyi44&s^R_C1oTu^) zDtG^Cq}Fdx$t;Uo8*QmNW0c^nS&Ns=qn)gt-d5e_o3cHfT3k`|s8@=t7T#99YOh78 z^e`3vttxja*@r!RNXf6C>FLpjETk94^U(>G|C8c}R;x=D{g) zX>o8fUlfN`RfKMHBsp`ijUK_F7eeo4%I0FFhKpPheqM!ex-mszG07pbtio~pVdoE7 z)YohiYcrcA7N4ycykHW3@U&}=ZON*=Arn61x-e?K0f+G&WRn5r3S_1so5A9SR2Jti zHl|wfdfVUx3V_zTwN)UW#bJgd&IS42ld9 zJ#&Dx?=1}%jL5VZu8c0)K~ODHv3MeeHCxtVDPnxF7QMDo2rUEKa&@32@u(UWIAoNI zYUR!&+DO7B$v=#aW;{_e;w-}>I~JVgCK-?)TgmBUx-%q*(*3=R5iT9u@Q9|n_Ny3n z#n)*Skn8_7;eIaTa5f?jYv)De_`TI&0q_<%cyG1OgJ&or^6#w@JR^lb5ewkG)!pBnu$bvi`@b$%+2ou4E_X+uDP2`z9z%-=Z<}SczIn-E5%PO&vVIj(o_K z8_%ghN6Xzbi>o@`sv7)IrbS9SzqU++aAI8oUv1vf<=w?O{Gg_T++rY-w(wqDyIT3M z#t6d1T8FeQ&MxK%$2DIdN$_l-g}mbQHZMZQA1~-AZ_1BX``iqKGSKnIt4CauQiP5_ zUd1`-WUWBQAFtkbEd76+j;N_;svIr6Nc(UY=w#VTjB|9AnqdSLr!<19gU<7-&@4q%3_+|Q@xJUx zP{=4={pC7rEkf&h$Bp#s4vSedjBhtdO{38xxlJ$Vd!zV$nga@kB;w5Clv&6vGE0&Z zftqo}7gn3m=OdsOw4)}LrjOF4Ga*LB=3%_ddG?%ad zAT6DaqT_J439=8O7^omd9HpE$NVzt8G-mz?{)o5Bg*p>b?;#P{-|jX@8=!7ez~ z4y#4fcx-nV>!7e~@u!IfQilyawd4*encyI=d7Nc5!5wZi=Ax%f7X&1Vo;F=RQHY*4 zT|QCm^LWVAqTxPKC1uRbvY0Tuei2iP(_q^5lJ{Qe``wL=>epFdCIwk!RNLy}Y8F|! zh^n*V4FTe(1hH-q7aQbWZ?w+~as&oG)QG^OWXnZ>Sy9F^7GAyf%hm6ku?#?m7->bs zFbxD_nYSSG%hg~kQ$+Lpay1ys@*sTrB{G&%8OxuJ#xf=Fpy*H4KCitqu_I95MC)pH;xI9 zit%r@$W#nWxc%1c{wO#YZrndhv`Y*JxYSx*P!vLp@d#H0PvhP2SR;%n!kpqXA7lXe zlO9q0S}we((%?EGhiJ?MDs1EwYAPy*#Qn?M&3UQ0p9QJu_ZFYnCC9>vubIPEWe-PZ z>Wx0?5D6xLN_i0`_*^v{e&a19{aiI1exnEze6C8MuI4I=%vGPO?(;oNip*7?tKK@q zIZ?>34XMvplYd{WdTrZM`h1m>j5n~=!)uMt7pt`${X1}=*&R_zT`I4p_EoF`ty81) zfWiM_b=!73iK-e@_;=N?$75xG(4B+O@x^~93DenC)u8r@p3DF}s2c_FheVQ}HD=OZ>mLvJf zMJjg2L^=3=k~PWNy#vF1JuQXR;0zlhtN z!NcxEvleM5XzN~!#DKVw`4oxqTTxr{)=1yyhaD)we7_~;`#tTY^lBuZttNN&|NEMW zx8Jl72Zn^qR01Z$?DrqqDm<=4LC8Yyw)}p1J z_*S^`BTULtHG*SgDO!}p1pG8TZ-={WO=&BS3yS{nop^?vZ6N}zi~{0^aAHYaVD~7S zU1(vMz+N5-TC_`eh{d<5;R)LwxvXmWg$%jFH;jk{Ppw%vDZO6-&oyt*x8mi}s{wC{ zyVs#=G#b>q*!Q*D@}dH+89q`UeAu$m7L|>SULmtQCi)BSfv&gLw$duQq}7gz@$R|a~T-r=Qjlw`64Q84vPbfx&<&v z?ehZmysW8S5$x?Xoa|rS8aG63)#b)0qZF1B+QM#!a! z8y1AG-mY7jXcu&rWQ}6?5D*7cpGnNN(e^5wE)R|_m$A-EkacIk@{C+-(ZX>RVGas4 zYvv0r#;6)X=ff;uV;81ITky13=g5a-ah>wKBiv@c%aOw(mHfFfg64?|@@~TnT%G@R!(_pfT>a znuL=%^Yi#+Z_wCzdUe?kc9?sL*8EcQZ+$x-j*uk(> zyGFX@+ECU(#WuUo8&<5wnuFd$Fl|vXp*5Mg? z=*$|q4;zs#@Xk6ku_?VFj!p)lbTs6|{G;6Dmz65>GvI;ZvUk=+la$6LYRk1~80e7M z{-6i-9t>!ZzkdDDn}_b8MF$>)X?1}gIBZgFt!|3?4E&6-5V5G%8BKYs8En_-`Ft)1 z0W(gV&*dO^do8$&nU0vR-d+nCF(}&fL`Dps-mA>#Z?9eB!C1UyK7V^{V1N3Y+?>6# z6y8RpXQR21X8 z3L?sL@UB{La8el}zN;1-oD?DAyK2F~Nf9ExtA>N~)kZ@gJzYx<_TOPAfBYVV7ny?A z$4Jq2K!~X6sNd5CYKpFVx)@(Y*F9ak+WkSjiK6SCu93_zfC9CrYc~zrW@WF=(!Q_u zo%6o{5LAH}Q6(iJ#o&M8EvUY)7W^+1LG^vL;D1q=!~1J5&1Pk<{4ejXh0RJz;6c&- zFBCE0-e1H2axEUNUVLUBsNvz-fkZ2{>rD2T8DZut>y@vJ*Y4h;1f+5iNLG|5XKTId z!?lio96UJ+WcY9mPmVlaTjeNt<~vAv077{nI^`)s`DY^KDbo5gk@6Iw{4m5GX7;6B}NXXtW2iD%VnMfJFTJ_KUyuRFB5|nr3DB`6HY*iP~~T$lu-no zpQ-ivIedx`@MmgdxcPnQ$!%H_d`x=sHwryzOSYFxO{y1uf`XaiCDf9(C7RPn8*213 z(n!vYj}ga;!lm$=e^Kk+n|@vFyIs=tX|eBf>)6*pYO~)BndhP>DkC=&+DKfC1jtor^hW>P9Xo@iOr^V3X z!R_LS&wK}Y5P+Zx#E7agYJ4X0AaAX`Ftt(y)z3s8RP5ESzC=8@T|D@!kq0S(2Sw*W zitymCMjqTz@A`bL>8sR)&(kZkZ;=wP~2xBensK;RHmy`iRBQG`gg=R1~ zk052I35+4)=YHE|4nC&r4(295j!g!no~rX7C9GMI6lIvi}hA)ig}Rp%o#Mrh5yN&zM%E{ z<>0trrn!g*mI-yrg27dd6T~{o;0tKqvN;=W7!gL{3c4U=3@xn7*gES{gde{YrHmqs z@TDka6k**j)k3f?MWpOYwGgaJ5h?ppjbL3#*$zqBZ+{1+41k~t#E7b-?6;$o@z&Z4 zQbrL}e>+Oq4tn*Kmq^NXNXotvrHm4IP;@Dyh?IRLN?BjM>vwA%e-WfiX+D3q)_qm_ z83$^6G)v9IH9GFIDZ4dd8!9(Vm9Yp7t zs}6V2gOihI<*Oj-gH)0}`0-}n6}Bm;y6FCtS9@I@Uz)LFJ?lI`C3o8cqm$b3oigpF zmhk!55nJ?_cC5~FIa>Z6?EC`a!krjh+UEK#PPA|}+GNmI^cZrzP_rATx^5f>X)3Y;7vD>;T3(F{gV`Ro2f+Cg z!MP{Eq5NM3XMgE?Q`qJPv=B-=@9bkhcAyAJ81SX1JQiqK#s5{HwUyn+>#ncYl5f;J zjF2)6#DmB{QhZ;pg}h_D1=`nZ*ZQ_NMHuKCwXSaSDo})hzESJ(GoBP-pl{T;3tT$7 zPwoEMcTh(I5LAH}QI$3RXHiG<7F7Q%TH_Q!_0OV??n5m8=S!rc`=q1)XVlS@z=NXe zXo`^Ge@aLH{VHqSo%OEo@9g-o&PxBEbM0R4;>)+pHDinL>NG zwufU$c7$M2H=GDzhiVefaXd5T=;tVaR4IJU&yci9wV=&P_h&nP1=yoU1Bk%`t}fVV z@f?+Oqg?Gcm!VF!1`ix?Z=s*bHsRRhj%>w;Su8@#5vcst-``0F#a~r;#7?d_>3Vx- z^243g{$KC_iSqHzHCc~}jU!8JqT=r%zE)z2L=zCV5)QxJ2hInK4RH>8Pc{Q%8UmEOnEAPs1sq7dK3sLv>)muAxZuKm_O7>N)m12?i7~q>`Xjc9VOsV zbd5t1jq}dV-d(2tC<5!9ox9zoP7z4&>}1E>(rNFMKKjwlWWUmBBOM_)AxuA;aTCXU z70-aUGLLzb07HtcP2ye5dIAKQN<6b2dxz|AkT}Ax5Y)Z^f;mi*Di8Em8Xs>7;SI5f zY>u$E8OO);KIp}+iBLb<8G-@8-H;FGo+#3_AMLzFSJ#M0sI;d$6Zb7sf=Co2B!vh+ zT|g2Up6;ysA#f@K>FG`mfjcBh@9qpaFn3y-35pStHZ(}|>dZgaeWyv}fpZFX2viYwn>rgMqDb?S$8Nz-yaj|xp{ zi#YPnlupuPOh5Xa%mc`}wRE4FA?|6E>X7imo%jRZwiD*6O9H>pNyf}S?qh;H*$T(n4og%<{|52p~Lp>K*H_^;Jg{*comwaX&X2R?$F6OFT(#Cs1tL9d$fu zG4dsXqLQ`dCij?b#IQ&kf~`pJt^!vaYX?`S6D&P7w@J9k>DqrcB+U_ z0y30%;zVhaxQoY8FzdI)u={MgIjA=&q@2Gu`49c0|Gnu=TJSgQRhCUn+Ye}w&1Tu$ zh51w-3G0b9YFY4PIxK|o^iX_q%`ftyQY8dH@t_G@3(;>tlk}bpl;g$DP*?oW}hIr&|v*qKvYgHSc3`B_zUY45tZbMBL{ML?E#rIMnwT~LW(#F9spaT z7MyV?I~7yMM)2)oEhdy zlMhK>)(cTKU{^B@P)BEaS=rEA+TbhGP;!W93b81&g7?Y{XG|J0tL2Cppkl(51O|c0 z!8%Zj&%VGdYzQl!mPL?+&vk}8zX1@5Ij-5!nT9z^t4YQ36AlKWuM~|&3zb#g6v5}l zpLCwMGloT>Uo4d@lLYkfWOz=cc$V5v$LrW4t{{X`$K-{t1^va*QIr^`7@RKk{2>`Xq~ zRW(bFGPS>`h0ynHVI@Ra7S3~>wS)aHbY)}FqL-G??SInK!F0c(jpl>S2#eEoGfM2E zrVU$=28Hc>@J9sfJipMzB#hABEy11rsrWOURN|FKz?=mdoq*3xmxD{Q- z9E0pGMN=Y<>x^y3eu<>($0=-$KVY56o;C_dJj}}if9j&g}j-pRg z3Z9$W*rK?KC9;~S;z$>FXOLJ6^ECv|I1`cSgagoia>H6K557h|XN`gfjGNZlbj2(= z7_vDilW9=y_>(;b(#^T3uk6T#2OhS`Y(toCYf*NU*8L`gh&WQ{t^#|&dhEGQZUoJ1 zAh^$U?s8|Z)j-gn>%^0q*TDbzT<5h1ENh|F@Z^TibzV1USqt-LteAOtQ@T4P?TJ)? zD!L-z=~XFNNQ$S{E{~P>Fn?@q-#nQ9yCi54YCSJ4@=G`p{dA&68=Mg z@_d;oW(~wzJn|7-2H=4>Sgf7mg|YRrVPpmupR2Bdj*eNI;y84L50*z-R^dHm6tZ zFq*2dww9fSk?G8;)in;YGPV{@EyE$NkDY=IY?oZZ(r|OmouN@7#QwH+(78>@uOc{~ zBV>%G2z^c04Gv8$U1&=SW(f-wkZ&W; znqm2IEuP$GfA}zYkF|i)GT2fuvtkJ*sxRdt9fl$e#SgfvEKid$4K^u3+RO9p$BWyk za&Q}UyohUJY>eT?G&Byjn@*LWcen@C2G}?^92w0GJW%BJ6%FgCua;O^%T&mxLX;HI z7p&CHiDLG~@zDJ;Z+@vDRz?|cczchAUG_R+-9|YYYmhW;mYV0 zqGh+mmf0YTiS>fczRXAEV0H|{=kbVjuF8ZxcN~Cy=yrC}l}66qwxXhnsSI>ZFpq|_ zgNLo_SAq=8037v#;~{>KkBRfEzXZ{{owwkhkaQ1q>}o3_t&sOP&&g%aJWb@p@lC|hA8JHjG;>P*Mbh>6F&*unB*c5~nC+CFuX=Fs>`_P<`qend!@NhSp zHkgoRZHpwKl4P)i2WV*jf|eOMn$C-?pg+>pi%sKiLGvSBd;A_HDl=vu>Ea$Gt)O>l z1^rLEl23*eG-Yc4(=Kf1cj!)S%Ieog;l2L9QC>i6Q1gYaPYvtFZ4IWbqRbk#b0a7( zPR-i144`P{uC<KzQH8d9)Q$8>l(Nz zy%dn5n;-Auif<0c6>buZvaLPDd9B%5ITPVozq~YfbJjAjR++d;Ox|6R1S8*2!r^L} zFiXfwJ<@W4ywu~z%cCjv`0=h?dvoNa4nN+-=I?Js9_7I&x@!CS{|ym$Ytd)5^rDeB z+$}+d?e_)pmytITSOA7C!vaZ)OqxoF#f`jpvh|-eN9?Z{rdI80UBoQ zN=tc`Pg&U58P8m@_oM&j69ZXgu=mLBSdnIjALP?pOjKi2xr;Y7db0=%KH5rzp(r9oJXVUnc+I z)_Qe&NBzjhYuKDimzQ;$SvEL*AUksV9WNUS0m9_9m}Y>olU(`83Q5;=k;dG{d<46j zp$~6Jd4(;ngl|WNZ_ftB6a(SazyZu1t)#U{Aj{439Wj+j4d7eQ#R8Hxh(SUlh4=a5 zOF#k)Y)qv;6ol54wsBZnv5P>-y)>a3CX^Yge)A6(|6ICsi2)a)Mz{sHiB4YHmTmd>U`wfgtWMdNp*|Cv{u?f~=506bgc!r3>heyW8M@~szk3pOt!b z*Omi%-(9a>)$uxvh?==8tgtohG(Q!uwkV#hD|oG}xINwCMPTT4sJw;J?uP@!Jskr9 zB9&C|N@>D{ zG-|0w8R9gp;!&(u_ieeE?(iE8P1RfdMVGz{mB9QuP6^uwbosh^|5TSLsXNm@COd05 zun=@{ueKzat#p}o|2I|J11Hk$XIEEF&M!Vbu4_)(EA=Dk7D*~U_f|~D?j0_H8!lBH zj}-@QOLslc1WKF#!_*Pf?zGdG+yHfsdtqR2S`(0ZUs_#VUa)sP`7 zNk)||VNnnejKw3rQc3IMPG5C+!Y@InPo`S}A?y3rV6>LJ{kzw^49yMfdE2l3-j9$2 zz4NdzJsJsHe?z*pOxSw1=IipSK=c0kni@ucfvXJqj`}vBw^t6o#Rlc6hkmw46IMsZ z?G?!Y^HoPju4|Glx9B0fsbifft1>Lru8y9HSWq$7)h?ocZou@HVPEa8UE5!m6PX!> z6+c?MO+<_@B&Yyech|afay2hA8)UUjDvRk0-Z*t>d|Rzo*93R!)6RNz8yCQY5@3}aCX{G= zTiv^h9oyGY_r@_C(ZgG%acZWfcGf(ckOI(m*7}q#xl`|ezLSJup~QDPEtRsp1NzRL z+?7PvFspWV>~-iEUDe$X`97eV%GyKMZqFu_u#wKV^MUDwIZi$Fm@v&Y@`JN~tuaNH zXpzR};KTP<83q)=XHTs=mS|~DkN1{$;IoI`s#jHY@#e?PQJ`5=F<-Ckg zG!$3&Y>V%Jay9vM)GNN~@3_``#pqT0ySEv1Q=9t;$rm^ZXxM$2p`irueFS@k5})m> zJ2y}h;QInMV5?r=kvVW8_^Q`;?=aw8gyC_;c6!hlfXyBm7z z))pn8-*D9)q22{|4@eN;?k?3D=S=HBh*odeex2^viqBY^-g4vMmh?{R2;y{G?Qm!REjU2zc2M(2k{0!w z^+khy8wcSXd+CGQu2LMRuxA&gdfS~Z4}B5O40R31zEo<~m+(X=*%JT^<$dud^hJ-n XFX0ib4&{BZXY^(0#^Eh-*86_}aPfhJ literal 0 HcmV?d00001 diff --git a/examples/servers/reading-list/service.pb.go b/examples/servers/reading-list/service.pb.go new file mode 100644 index 000000000..c7ec52a58 --- /dev/null +++ b/examples/servers/reading-list/service.pb.go @@ -0,0 +1,292 @@ +// Code generated by protoc-gen-go. +// source: service.proto +// DO NOT EDIT! + +/* +Package readinglist is a generated protocol buffer package. + +It is generated from these files: + service.proto + +It has these top-level messages: + PutLinkRequest + GetListLimitRequest + Link + LinkRequest + Links + Message +*/ +package readinglist + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type PutLinkRequest struct { + // The Link to save + Request *LinkRequest `protobuf:"bytes,1,opt,name=request" json:"request,omitempty"` +} + +func (m *PutLinkRequest) Reset() { *m = PutLinkRequest{} } +func (m *PutLinkRequest) String() string { return proto.CompactTextString(m) } +func (*PutLinkRequest) ProtoMessage() {} +func (*PutLinkRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *PutLinkRequest) GetRequest() *LinkRequest { + if m != nil { + return m.Request + } + return nil +} + +type GetListLimitRequest struct { + // The limit of links to fetch + Limit int32 `protobuf:"varint,1,opt,name=limit" json:"limit,omitempty"` +} + +func (m *GetListLimitRequest) Reset() { *m = GetListLimitRequest{} } +func (m *GetListLimitRequest) String() string { return proto.CompactTextString(m) } +func (*GetListLimitRequest) ProtoMessage() {} +func (*GetListLimitRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *GetListLimitRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +type Link struct { + Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"` +} + +func (m *Link) Reset() { *m = Link{} } +func (m *Link) String() string { return proto.CompactTextString(m) } +func (*Link) ProtoMessage() {} +func (*Link) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *Link) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +type LinkRequest struct { + // True to remove this link from the user's list. + Delete bool `protobuf:"varint,1,opt,name=delete" json:"delete,omitempty"` + Link *Link `protobuf:"bytes,2,opt,name=link" json:"link,omitempty"` +} + +func (m *LinkRequest) Reset() { *m = LinkRequest{} } +func (m *LinkRequest) String() string { return proto.CompactTextString(m) } +func (*LinkRequest) ProtoMessage() {} +func (*LinkRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *LinkRequest) GetDelete() bool { + if m != nil { + return m.Delete + } + return false +} + +func (m *LinkRequest) GetLink() *Link { + if m != nil { + return m.Link + } + return nil +} + +type Links struct { + Links []*Link `protobuf:"bytes,1,rep,name=links" json:"links,omitempty"` +} + +func (m *Links) Reset() { *m = Links{} } +func (m *Links) String() string { return proto.CompactTextString(m) } +func (*Links) ProtoMessage() {} +func (*Links) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *Links) GetLinks() []*Link { + if m != nil { + return m.Links + } + return nil +} + +type Message struct { + Message string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *Message) GetMessage() string { + if m != nil { + return m.Message + } + return "" +} + +func init() { + proto.RegisterType((*PutLinkRequest)(nil), "readinglist.PutLinkRequest") + proto.RegisterType((*GetListLimitRequest)(nil), "readinglist.GetListLimitRequest") + proto.RegisterType((*Link)(nil), "readinglist.Link") + proto.RegisterType((*LinkRequest)(nil), "readinglist.LinkRequest") + proto.RegisterType((*Links)(nil), "readinglist.Links") + proto.RegisterType((*Message)(nil), "readinglist.Message") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for ReadingListService service + +type ReadingListServiceClient interface { + // Save a link via JSON or Protobuf + PutLink(ctx context.Context, in *PutLinkRequest, opts ...grpc.CallOption) (*Message, error) + // Get your links via JSON or Protobuf + GetListLimit(ctx context.Context, in *GetListLimitRequest, opts ...grpc.CallOption) (*Links, error) +} + +type readingListServiceClient struct { + cc *grpc.ClientConn +} + +func NewReadingListServiceClient(cc *grpc.ClientConn) ReadingListServiceClient { + return &readingListServiceClient{cc} +} + +func (c *readingListServiceClient) PutLink(ctx context.Context, in *PutLinkRequest, opts ...grpc.CallOption) (*Message, error) { + out := new(Message) + err := grpc.Invoke(ctx, "/readinglist.ReadingListService/PutLink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *readingListServiceClient) GetListLimit(ctx context.Context, in *GetListLimitRequest, opts ...grpc.CallOption) (*Links, error) { + out := new(Links) + err := grpc.Invoke(ctx, "/readinglist.ReadingListService/GetListLimit", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ReadingListService service + +type ReadingListServiceServer interface { + // Save a link via JSON or Protobuf + PutLink(context.Context, *PutLinkRequest) (*Message, error) + // Get your links via JSON or Protobuf + GetListLimit(context.Context, *GetListLimitRequest) (*Links, error) +} + +func RegisterReadingListServiceServer(s *grpc.Server, srv ReadingListServiceServer) { + s.RegisterService(&_ReadingListService_serviceDesc, srv) +} + +func _ReadingListService_PutLink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutLinkRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReadingListServiceServer).PutLink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/readinglist.ReadingListService/PutLink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReadingListServiceServer).PutLink(ctx, req.(*PutLinkRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReadingListService_GetListLimit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetListLimitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReadingListServiceServer).GetListLimit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/readinglist.ReadingListService/GetListLimit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReadingListServiceServer).GetListLimit(ctx, req.(*GetListLimitRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ReadingListService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "readinglist.ReadingListService", + HandlerType: (*ReadingListServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PutLink", + Handler: _ReadingListService_PutLink_Handler, + }, + { + MethodName: "GetListLimit", + Handler: _ReadingListService_GetListLimit_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "service.proto", +} + +func init() { proto.RegisterFile("service.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 331 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x52, 0xc1, 0x4a, 0xeb, 0x40, + 0x14, 0x25, 0x6d, 0xd3, 0xbc, 0x77, 0x6b, 0xb5, 0x5e, 0x6b, 0x09, 0xd1, 0x45, 0x19, 0x11, 0x05, + 0xa1, 0x91, 0xba, 0x73, 0x2d, 0xb8, 0x89, 0x20, 0xd1, 0x9d, 0xab, 0x68, 0x2f, 0x61, 0x68, 0x3a, + 0xa9, 0x99, 0x89, 0x1b, 0x71, 0xe3, 0x2f, 0xf8, 0x51, 0x7e, 0x80, 0xbf, 0xe0, 0x87, 0xc8, 0xcc, + 0x24, 0x90, 0xd0, 0xee, 0xee, 0xc9, 0x39, 0x73, 0xe6, 0xdc, 0x93, 0x81, 0xa1, 0xa4, 0xe2, 0x8d, + 0xbf, 0xd0, 0x6c, 0x5d, 0xe4, 0x2a, 0xc7, 0x41, 0x41, 0xc9, 0x82, 0x8b, 0x34, 0xe3, 0x52, 0x05, + 0xc7, 0x69, 0x9e, 0xa7, 0x19, 0x85, 0xc9, 0x9a, 0x87, 0x89, 0x10, 0xb9, 0x4a, 0x14, 0xcf, 0x85, + 0xb4, 0x52, 0x76, 0x03, 0xbb, 0xf7, 0xa5, 0x8a, 0xb8, 0x58, 0xc6, 0xf4, 0x5a, 0x92, 0x54, 0x38, + 0x07, 0xaf, 0xb0, 0xa3, 0xef, 0x4c, 0x9d, 0xf3, 0xc1, 0xdc, 0x9f, 0x35, 0xec, 0x66, 0x0d, 0x69, + 0x5c, 0x0b, 0xd9, 0x05, 0x1c, 0xdc, 0x92, 0x8a, 0xb8, 0x54, 0x11, 0x5f, 0x71, 0x55, 0x5b, 0x8d, + 0xc1, 0xcd, 0x34, 0x36, 0x46, 0x6e, 0x6c, 0x01, 0xf3, 0xa1, 0xa7, 0x4d, 0x70, 0x04, 0xdd, 0xb2, + 0xc8, 0x0c, 0xf7, 0x3f, 0xd6, 0x23, 0x8b, 0x60, 0xd0, 0x4c, 0x32, 0x81, 0xfe, 0x82, 0x32, 0x52, + 0x64, 0x34, 0xff, 0xe2, 0x0a, 0xe1, 0x29, 0xf4, 0x32, 0x2e, 0x96, 0x7e, 0xc7, 0xc4, 0xdb, 0xdf, + 0x8c, 0x67, 0x68, 0x76, 0x09, 0xae, 0x46, 0x12, 0xcf, 0x74, 0x0c, 0xb1, 0x94, 0xbe, 0x33, 0xed, + 0x6e, 0x3f, 0x60, 0x79, 0x76, 0x02, 0xde, 0x1d, 0x49, 0x99, 0xa4, 0x84, 0x3e, 0x78, 0x2b, 0x3b, + 0x56, 0x01, 0x6b, 0x38, 0xff, 0x76, 0x00, 0x63, 0x6b, 0xa0, 0x17, 0x7e, 0xb0, 0xcd, 0xe3, 0x23, + 0x78, 0x55, 0x91, 0x78, 0xd4, 0xba, 0xa0, 0x5d, 0x6f, 0x30, 0x6e, 0x91, 0xd5, 0x75, 0x6c, 0xf2, + 0xf9, 0xf3, 0xfb, 0xd5, 0x19, 0x05, 0x6e, 0xa8, 0x93, 0x5c, 0xd7, 0xc5, 0xe2, 0x13, 0xec, 0x34, + 0x8b, 0xc5, 0x69, 0xeb, 0xf4, 0x96, 0xce, 0x03, 0xdc, 0xd8, 0x4e, 0xb2, 0x43, 0xe3, 0xbe, 0x87, + 0xc3, 0x50, 0x7f, 0x0c, 0xdf, 0xcd, 0x7f, 0xf8, 0x78, 0xee, 0x9b, 0x27, 0x70, 0xf5, 0x17, 0x00, + 0x00, 0xff, 0xff, 0x2a, 0x51, 0x19, 0x75, 0x3e, 0x02, 0x00, 0x00, +} diff --git a/examples/servers/reading-list/service.proto b/examples/servers/reading-list/service.proto new file mode 100644 index 000000000..f43747092 --- /dev/null +++ b/examples/servers/reading-list/service.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; + +package readinglist; + +message PutLinkRequest { + // The Link to save + LinkRequest request = 1; +} + +message GetListLimitRequest { + // The limit of links to fetch + int32 limit = 1; +} + +message Link { + string url = 1; +} + +message LinkRequest { + // True to remove this link from the user's list. + bool delete = 1; + Link link = 2; +} + +message Links { + repeated Link links = 1; +} + +message Message { + string message = 1; +} + +service ReadingListService { + // Save a link via JSON or Protobuf + rpc PutLink(PutLinkRequest) returns (Message) { + option (google.api.http) = { + put: "/link" + body: "request" + }; + } + // Get your links via JSON or Protobuf + rpc GetListLimit(GetListLimitRequest) returns (Links) { + option (google.api.http) = { + get: "/list/{limit}" + }; + } +} diff --git a/examples/servers/reading-list/service.yaml b/examples/servers/reading-list/service.yaml new file mode 100644 index 000000000..467935cb1 --- /dev/null +++ b/examples/servers/reading-list/service.yaml @@ -0,0 +1,94 @@ +swagger: '2.0' + +info: + version: "0.0.0" + title: Reading List + description: Reading list + +paths: + /list/{limit}: + get: + description: | + Get your links via JSON or Protobuf + produces: + - application/json + parameters: + - name: limit + description: The limit of links to fetch + type: number + format: int32 + x-proto-tag: 1 + responses: + 200: + description: Successful link retrieval + schema: + $ref: '#/definitions/Links' + 401: + description: No cookie + schema: + $ref: '#/definitions/Message' + 500: + description: We had a server error + schema: + $ref: '#/definitions/Message' + + /link: + put: + description: | + Save a link via JSON or Protobuf + produces: + - application/json + parameters: + - name: request + in: body + required: true + description: The Link to save + schema: + $ref: '#/definitions/LinkRequest' + x-proto-tag: 1 + responses: + 201: + description: Successful link save + schema: + $ref: '#/definitions/Message' + 401: + description: No cookie + schema: + $ref: '#/definitions/Message' + 500: + description: Server problems + schema: + $ref: '#/definitions/Message' + +definitions: + Links: + type: object + properties: + links: + type: array + items: + $ref: '#/definitions/Link' + x-proto-tag: 1 + + Link: + type: object + properties: + url: + type: string + x-proto-tag: 1 + + LinkRequest: + type: object + properties: + link: + $ref: '#/definitions/Link' + delete: + type: boolean + description: True to remove this link from the user's list. + + Message: + type: object + properties: + message: + type: string + x-proto-tag: 1 diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/.travis.yml b/examples/servers/reading-list/vendor/cloud.google.com/go/.travis.yml new file mode 100644 index 000000000..8c769d71f --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/.travis.yml @@ -0,0 +1,16 @@ +sudo: false +language: go +go: +- 1.6 +- 1.7 +- 1.8 +install: +- go get -v cloud.google.com/go/... +script: +- openssl aes-256-cbc -K $encrypted_a8b3f4fc85f4_key -iv $encrypted_a8b3f4fc85f4_iv -in key.json.enc -out key.json -d +- GCLOUD_TESTS_GOLANG_PROJECT_ID="dulcet-port-762" GCLOUD_TESTS_GOLANG_KEY="$(pwd)/key.json" + ./run-tests.sh $TRAVIS_COMMIT +env: + matrix: + # The GCLOUD_TESTS_API_KEY environment variable. + secure: VdldogUOoubQ60LhuHJ+g/aJoBiujkSkWEWl79Zb8cvQorcQbxISS+JsOOp4QkUOU4WwaHAm8/3pIH1QMWOR6O78DaLmDKi5Q4RpkVdCpUXy+OAfQaZIcBsispMrjxLXnqFjo9ELnrArfjoeCTzaX0QTCfwQwVmigC8rR30JBKI= diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/AUTHORS b/examples/servers/reading-list/vendor/cloud.google.com/go/AUTHORS new file mode 100644 index 000000000..c364af1da --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/AUTHORS @@ -0,0 +1,15 @@ +# This is the official list of cloud authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Filippo Valsorda +Google Inc. +Ingo Oeser +Palm Stone Games, Inc. +PaweÅ‚ Knap +Péter Szilágyi +Tyler Treat diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTING.md b/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTING.md new file mode 100644 index 000000000..aed05dc4f --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTING.md @@ -0,0 +1,138 @@ +# Contributing + +1. Sign one of the contributor license agreements below. +1. `go get golang.org/x/review/git-codereview` to install the code reviewing tool. + 1. You will need to ensure that your `GOBIN` directory (by default + `$GOPATH/bin`) is in your `PATH` so that git can find the command. + 1. If you would like, you may want to set up aliases for git-codereview, + such that `git codereview change` becomes `git change`. See the + [godoc](https://godoc.org/golang.org/x/review/git-codereview) for details. + 1. Should you run into issues with the git-codereview tool, please note + that all error messages will assume that you have set up these + aliases. +1. Get the cloud package by running `go get -d cloud.google.com/go`. + 1. If you have already checked out the source, make sure that the remote git + origin is https://code.googlesource.com/gocloud: + + git remote set-url origin https://code.googlesource.com/gocloud +1. Make sure your auth is configured correctly by visiting + https://code.googlesource.com, clicking "Generate Password", and following + the directions. +1. Make changes and create a change by running `git codereview change `, +provide a commit message, and use `git codereview mail` to create a Gerrit CL. +1. Keep amending to the change with `git codereview change` and mail as your receive +feedback. Each new mailed amendment will create a new patch set for your change in Gerrit. + +## Integration Tests + +In addition to the unit tests, you may run the integration test suite. + +To run the integrations tests, creating and configuration of a project in the +Google Developers Console is required. + +After creating a project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount). +Ensure the project-level **Owner** [IAM role](console.cloud.google.com/iam-admin/iam/project) +(or **Editor** and **Logs Configuration Writer** roles) are added to the +service account. + +Once you create a project, set the following environment variables to be able to +run the against the actual APIs. + +- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455) +- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file. +- **GCLOUD_TESTS_API_KEY**: Your API key. + +Install the [gcloud command-line tool][gcloudcli] to your machine and use it +to create some resources used in integration tests. + +From the project's root directory: + +``` sh +# Set the default project in your env. +$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID + +# Authenticate the gcloud tool with your account. +$ gcloud auth login + +# Create the indexes used in the datastore integration tests. +$ gcloud preview datastore create-indexes datastore/testdata/index.yaml + +# Create a Google Cloud storage bucket with the same name as your test project, +# and with the Stackdriver Logging service account as owner, for the sink +# integration tests in logging. +$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID +$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID + +# Create a Spanner instance for the spanner integration tests. +$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 1 --description 'Instance for go client test' +# NOTE: Spanner instances are priced by the node-hour, so you may want to delete +# the instance after testing with 'gcloud beta spanner instances delete'. +``` + +Once you've set the environment variables, you can run the integration tests by +running: + +``` sh +$ go test -v cloud.google.com/go/... +``` + +## Contributor License Agreements + +Before we can accept your pull requests you'll need to sign a Contributor +License Agreement (CLA): + +- **If you are an individual writing original source code** and **you own the +- intellectual property**, then you'll need to sign an [individual CLA][indvcla]. +- **If you work for a company that wants to allow you to contribute your work**, +then you'll need to sign a [corporate CLA][corpcla]. + +You can sign these electronically (just scroll to the bottom). After that, +we'll be able to accept your pull requests. + +## Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) + +[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/ +[indvcla]: https://developers.google.com/open-source/cla/individual +[corpcla]: https://developers.google.com/open-source/cla/corporate diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTORS b/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTORS new file mode 100644 index 000000000..d4b376c7c --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/CONTRIBUTORS @@ -0,0 +1,37 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# Names should be added to this file as: +# Name + +# Keep the list alphabetically sorted. + +Alexis Hunt +Andreas Litt +Andrew Gerrand +Brad Fitzpatrick +Burcu Dogan +Dave Day +David Sansome +David Symonds +Filippo Valsorda +Glenn Lewis +Ingo Oeser +Johan Euphrosine +Jonathan Amsterdam +Luna Duclos +Magnus Hiie +Michael McGreevy +Omar Jarjur +PaweÅ‚ Knap +Péter Szilágyi +Sarah Adams +Thanatat Tamtan +Toby Burress +Tuo Shan +Tyler Treat diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/LICENSE b/examples/servers/reading-list/vendor/cloud.google.com/go/LICENSE new file mode 100644 index 000000000..a4c5efd82 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/README.md b/examples/servers/reading-list/vendor/cloud.google.com/go/README.md new file mode 100644 index 000000000..adc54a23d --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/README.md @@ -0,0 +1,442 @@ +# Google Cloud Client Libraries for Go + +[![GoDoc](https://godoc.org/cloud.google.com/go?status.svg)](https://godoc.org/cloud.google.com/go) + +Go packages for [Google Cloud Platform](https://cloud.google.com) services. + +``` go +import "cloud.google.com/go" +``` + +To install the packages on your system, + +``` +$ go get -u cloud.google.com/go/... +``` + +**NOTE:** Some of these packages are under development, and may occasionally +make backwards-incompatible changes. + +**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud). + + * [News](#news) + * [Supported APIs](#supported-apis) + * [Go Versions Supported](#go-versions-supported) + * [Authorization](#authorization) + * [Cloud Datastore](#cloud-datastore-) + * [Cloud Storage](#cloud-storage-) + * [Cloud Pub/Sub](#cloud-pub-sub-) + * [Cloud BigQuery](#cloud-bigquery-) + * [Stackdriver Logging](#stackdriver-logging-) + * [Cloud Spanner](#cloud-spanner-) + + +## News + +_March 17, 2017_ + +Breaking Pubsub changes. +* Publish is now asynchronous +([announcement](https://groups.google.com/d/topic/google-api-go-announce/aaqRDIQ3rvU/discussion)). +* Subscription.Pull replaced by Subscription.Receive, which takes a callback ([announcement](https://groups.google.com/d/topic/google-api-go-announce/8pt6oetAdKc/discussion)). +* Message.Done replaced with Message.Ack and Message.Nack. + +_February 14, 2017_ + +Release of a client library for Spanner. See +the +[blog post](https://cloudplatform.googleblog.com/2017/02/introducing-Cloud-Spanner-a-global-database-service-for-mission-critical-applications.html). + +Note that although the Spanner service is beta, the Go client library is alpha. + + +[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md) + +## Supported APIs + +Google API | Status | Package +---------------------------------|--------------|----------------------------------------------------------- +[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref] +[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref] +[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref] +[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref] +[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref] +[Monitoring][cloud-monitoring] | alpha | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref] +[Pub/Sub][cloud-pubsub] | alpha | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref] +[Vision][cloud-vision] | beta | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref] +[Language][cloud-language] | beta | [`cloud.google.com/go/language/apiv1`][cloud-language-ref] +[Speech][cloud-speech] | beta | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref] +[Spanner][cloud-spanner] | alpha | [`cloud.google.com/go/spanner`][cloud-spanner-ref] +[Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref] +[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace`][cloud-trace-ref] +[Video Intelligence][cloud-video]| alpha | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref] +[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errors`][cloud-errors-ref] + + +> **Alpha status**: the API is still being actively developed. As a +> result, it might change in backward-incompatible ways and is not recommended +> for production use. +> +> **Beta status**: the API is largely complete, but still has outstanding +> features and bugs to be addressed. There may be minor backwards-incompatible +> changes where necessary. +> +> **Stable status**: the API is mature and ready for production use. We will +> continue addressing bugs and feature requests. + +Documentation and examples are available at +https://godoc.org/cloud.google.com/go + +Visit or join the +[google-api-go-announce group](https://groups.google.com/forum/#!forum/google-api-go-announce) +for updates on these packages. + +## Go Versions Supported + +We support the two most recent major versions of Go. If Google App Engine uses +an older version, we support that as well. You can see which versions are +currently supported by looking at the lines following `go:` in +[`.travis.yml`](.travis.yml). + +## Authorization + +By default, each API will use [Google Application Default Credentials][default-creds] +for authorization credentials used in calling the API endpoints. This will allow your +application to run in many environments without requiring explicit configuration. + +[snip]:# (auth) +```go +client, err := storage.NewClient(ctx) +``` + +To authorize using a +[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys), +pass +[`option.WithServiceAccountFile`](https://godoc.org/google.golang.org/api/option#WithServiceAccountFile) +to the `NewClient` function of the desired package. For example: + +[snip]:# (auth-JSON) +```go +client, err := storage.NewClient(ctx, option.WithServiceAccountFile("path/to/keyfile.json")) +``` + +You can exert more control over authorization by using the +[`golang.org/x/oauth2`](https://godoc.org/golang.org/x/oauth2) package to +create an `oauth2.TokenSource`. Then pass +[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource) +to the `NewClient` function: +[snip]:# (auth-ts) +```go +tokenSource := ... +client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource)) +``` + +## Cloud Datastore [![GoDoc](https://godoc.org/cloud.google.com/go/datastore?status.svg)](https://godoc.org/cloud.google.com/go/datastore) + +- [About Cloud Datastore][cloud-datastore] +- [Activating the API for your project][cloud-datastore-activation] +- [API documentation][cloud-datastore-docs] +- [Go client documentation](https://godoc.org/cloud.google.com/go/datastore) +- [Complete sample program](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/datastore/tasks) + +### Example Usage + +First create a `datastore.Client` to use throughout your application: + +[snip]:# (datastore-1) +```go +client, err := datastore.NewClient(ctx, "my-project-id") +if err != nil { + log.Fatal(err) +} +``` + +Then use that client to interact with the API: + +[snip]:# (datastore-2) +```go +type Post struct { + Title string + Body string `datastore:",noindex"` + PublishedAt time.Time +} +keys := []*datastore.Key{ + datastore.NameKey("Post", "post1", nil), + datastore.NameKey("Post", "post2", nil), +} +posts := []*Post{ + {Title: "Post 1", Body: "...", PublishedAt: time.Now()}, + {Title: "Post 2", Body: "...", PublishedAt: time.Now()}, +} +if _, err := client.PutMulti(ctx, keys, posts); err != nil { + log.Fatal(err) +} +``` + +## Cloud Storage [![GoDoc](https://godoc.org/cloud.google.com/go/storage?status.svg)](https://godoc.org/cloud.google.com/go/storage) + +- [About Cloud Storage][cloud-storage] +- [API documentation][cloud-storage-docs] +- [Go client documentation](https://godoc.org/cloud.google.com/go/storage) +- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage) + +### Example Usage + +First create a `storage.Client` to use throughout your application: + +[snip]:# (storage-1) +```go +client, err := storage.NewClient(ctx) +if err != nil { + log.Fatal(err) +} +``` + +[snip]:# (storage-2) +```go +// Read the object1 from bucket. +rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx) +if err != nil { + log.Fatal(err) +} +defer rc.Close() +body, err := ioutil.ReadAll(rc) +if err != nil { + log.Fatal(err) +} +``` + +## Cloud Pub/Sub [![GoDoc](https://godoc.org/cloud.google.com/go/pubsub?status.svg)](https://godoc.org/cloud.google.com/go/pubsub) + +- [About Cloud Pubsub][cloud-pubsub] +- [API documentation][cloud-pubsub-docs] +- [Go client documentation](https://godoc.org/cloud.google.com/go/pubsub) +- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/pubsub) + +### Example Usage + +First create a `pubsub.Client` to use throughout your application: + +[snip]:# (pubsub-1) +```go +client, err := pubsub.NewClient(ctx, "project-id") +if err != nil { + log.Fatal(err) +} +``` + +Then use the client to publish and subscribe: + +[snip]:# (pubsub-2) +```go +// Publish "hello world" on topic1. +topic := client.Topic("topic1") +res := topic.Publish(ctx, &pubsub.Message{ + Data: []byte("hello world"), +}) +// The publish happens asynchronously. +// Later, you can get the result from res: +... +msgID, err := res.Get(ctx) +if err != nil { + log.Fatal(err) +} + +// Use a callback to receive messages via subscription1. +sub := client.Subscription("subscription1") +err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) { + fmt.Println(m.Data) + m.Ack() // Acknowledge that we've consumed the message. +}) +if err != nil { + log.Println(err) +} +``` + +## Cloud BigQuery [![GoDoc](https://godoc.org/cloud.google.com/go/bigquery?status.svg)](https://godoc.org/cloud.google.com/go/bigquery) + +- [About Cloud BigQuery][cloud-bigquery] +- [API documentation][cloud-bigquery-docs] +- [Go client documentation][cloud-bigquery-ref] +- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/bigquery) + +### Example Usage + +First create a `bigquery.Client` to use throughout your application: +[snip]:# (bq-1) +```go +c, err := bigquery.NewClient(ctx, "my-project-ID") +if err != nil { + // TODO: Handle error. +} +``` + +Then use that client to interact with the API: +[snip]:# (bq-2) +```go +// Construct a query. +q := c.Query(` + SELECT year, SUM(number) + FROM [bigquery-public-data:usa_names.usa_1910_2013] + WHERE name = "William" + GROUP BY year + ORDER BY year +`) +// Execute the query. +it, err := q.Read(ctx) +if err != nil { + // TODO: Handle error. +} +// Iterate through the results. +for { + var values []bigquery.Value + err := it.Next(&values) + if err == iterator.Done { + break + } + if err != nil { + // TODO: Handle error. + } + fmt.Println(values) +} +``` + + +## Stackdriver Logging [![GoDoc](https://godoc.org/cloud.google.com/go/logging?status.svg)](https://godoc.org/cloud.google.com/go/logging) + +- [About Stackdriver Logging][cloud-logging] +- [API documentation][cloud-logging-docs] +- [Go client documentation][cloud-logging-ref] +- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/logging) + +### Example Usage + +First create a `logging.Client` to use throughout your application: +[snip]:# (logging-1) +```go +ctx := context.Background() +client, err := logging.NewClient(ctx, "my-project") +if err != nil { + // TODO: Handle error. +} +``` + +Usually, you'll want to add log entries to a buffer to be periodically flushed +(automatically and asynchronously) to the Stackdriver Logging service. +[snip]:# (logging-2) +```go +logger := client.Logger("my-log") +logger.Log(logging.Entry{Payload: "something happened!"}) +``` + +Close your client before your program exits, to flush any buffered log entries. +[snip]:# (logging-3) +```go +err = client.Close() +if err != nil { + // TODO: Handle error. +} +``` + +## Cloud Spanner [![GoDoc](https://godoc.org/cloud.google.com/go/spanner?status.svg)](https://godoc.org/cloud.google.com/go/spanner) + +- [About Cloud Spanner][cloud-spanner] +- [API documentation][cloud-spanner-docs] +- [Go client documentation](https://godoc.org/cloud.google.com/go/spanner) + +### Example Usage + +First create a `spanner.Client` to use throughout your application: + +[snip]:# (spanner-1) +```go +client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D") +if err != nil { + log.Fatal(err) +} +``` + +[snip]:# (spanner-2) +```go +// Simple Reads And Writes +_, err = client.Apply(ctx, []*spanner.Mutation{ + spanner.Insert("Users", + []string{"name", "email"}, + []interface{}{"alice", "a@example.com"})}) +if err != nil { + log.Fatal(err) +} +row, err := client.Single().ReadRow(ctx, "Users", + spanner.Key{"alice"}, []string{"email"}) +if err != nil { + log.Fatal(err) +} +``` + + +## Contributing + +Contributions are welcome. Please, see the +[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md) +document for details. We're using Gerrit for our code reviews. Please don't open pull +requests against this repo, new pull requests will be automatically closed. + +Please note that this project is released with a Contributor Code of Conduct. +By participating in this project you agree to abide by its terms. +See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct) +for more information. + +[cloud-datastore]: https://cloud.google.com/datastore/ +[cloud-datastore-ref]: https://godoc.org/cloud.google.com/go/datastore +[cloud-datastore-docs]: https://cloud.google.com/datastore/docs +[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate + +[cloud-pubsub]: https://cloud.google.com/pubsub/ +[cloud-pubsub-ref]: https://godoc.org/cloud.google.com/go/pubsub +[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs + +[cloud-storage]: https://cloud.google.com/storage/ +[cloud-storage-ref]: https://godoc.org/cloud.google.com/go/storage +[cloud-storage-docs]: https://cloud.google.com/storage/docs +[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets + +[cloud-bigtable]: https://cloud.google.com/bigtable/ +[cloud-bigtable-ref]: https://godoc.org/cloud.google.com/go/bigtable + +[cloud-bigquery]: https://cloud.google.com/bigquery/ +[cloud-bigquery-docs]: https://cloud.google.com/bigquery/docs +[cloud-bigquery-ref]: https://godoc.org/cloud.google.com/go/bigquery + +[cloud-logging]: https://cloud.google.com/logging/ +[cloud-logging-docs]: https://cloud.google.com/logging/docs +[cloud-logging-ref]: https://godoc.org/cloud.google.com/go/logging + +[cloud-monitoring]: https://cloud.google.com/monitoring/ +[cloud-monitoring-ref]: https://godoc.org/cloud.google.com/go/monitoring/apiv3 + +[cloud-vision]: https://cloud.google.com/vision +[cloud-vision-ref]: https://godoc.org/cloud.google.com/go/vision/apiv1 + +[cloud-language]: https://cloud.google.com/natural-language +[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1 + +[cloud-speech]: https://cloud.google.com/speech +[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1 + +[cloud-spanner]: https://cloud.google.com/spanner/ +[cloud-spanner-ref]: https://godoc.org/cloud.google.com/go/spanner +[cloud-spanner-docs]: https://cloud.google.com/spanner/docs + +[cloud-translation]: https://cloud.google.com/translation +[cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation + +[cloud-trace]: https://cloud.google.com/trace/ +[cloud-trace-ref]: https://godoc.org/cloud.google.com/go/trace + +[cloud-video]: https://cloud.google.com/video-intelligence/ +[cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1 + +[cloud-errors]: https://cloud.google.com/error-reporting/ +[cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errors + +[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/appveyor.yml b/examples/servers/reading-list/vendor/cloud.google.com/go/appveyor.yml new file mode 100644 index 000000000..e66cd00af --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/appveyor.yml @@ -0,0 +1,32 @@ +# This file configures AppVeyor (http://www.appveyor.com), +# a Windows-based CI service similar to Travis. + +# Identifier for this run +version: "{build}" + +# Clone the repo into this path, which conforms to the standard +# Go workspace structure. +clone_folder: c:\gopath\src\cloud.google.com\go + +environment: + GOPATH: c:\gopath + GCLOUD_TESTS_GOLANG_PROJECT_ID: dulcet-port-762 + GCLOUD_TESTS_GOLANG_KEY: c:\gopath\src\cloud.google.com\go\key.json + KEYFILE_CONTENTS: + secure: IvRbDAhM2PIQqzVkjzJ4FjizUvoQ+c3vG/qhJQG+HlZ/L5KEkqLu+x6WjLrExrNMyGku4znB2jmbTrUW3Ob4sGG+R5vvqeQ3YMHCVIkw5CxY+/bUDkW5RZWsVbuCnNa/vKsWmCP+/sZW6ICe29yKJ2ZOb6QaauI4s9R6j+cqBbU9pumMGYFRb0Rw3uUU7DKmVFCy+NjTENZIlDP9rmjANgAzigowJJEb2Tg9sLlQKmQeKiBSRN8lKc5Nq60a+fIzHGKvql4eIitDDDpOpyHv15/Xr1BzFw2yDoiR4X1lng0u7q0X9RgX4VIYa6gT16NXBEmQgbuX8gh7SfPMp9RhiZD9sVUaV+yogEabYpyPnmUURo0hXwkctKaBkQlEmKvjHwF5dvbg8+yqGhwtjAgFNimXG3INrwQsfQsZskkQWanutbJf9xy50GyWWFZZdi0uT4oXP/b5P7aklPXKXsvrJKBh7RjEaqBrhi86IJwOjBspvoR4l2WmcQyxb2xzQS1pjbBJFQfYJJ8+JgsstTL8PBO9d4ybJC0li1Om1qnWxkaewvPxxuoHJ9LpRKof19yRYWBmhTXb2tTASKG/zslvl4fgG4DmQBS93WC7dsiGOhAraGw2eCTgd0lYZOhk1FjWl9TS80aktXxzH/7nTvem5ohm+eDl6O0wnTL4KXjQVNSQ1PyLn4lGRJ5MNGzBTRFWIr2API2rca4Fysyfh/UdmazPGlNbY9JPGqb9+F04QzLfqm+Zz/cHy59E7lOSMBlUI4KD6d6ZNNKNRH+/g9i+fSiyiXKugTfda8KBnWGyPwprxuWGYaiQUGUYOwJY5R6x5c4mjImAB310V+Wo33UbWFJiwxEDsiCNqW1meVkBzt2er26vh4qbgCUIQ3iM3gFPfHgy+QxkmIhic7Q1HYacQElt8AAP41M7cCKWCuZidegP37MBB//mjjiNt047ZSQEvB4tqsX/OvfbByVef+cbtVw9T0yjHvmCdPW1XrhyrCCgclu6oYYdbmc5D7BBDRbjjMWGv6YvceAbfGf6ukdB5PuV+TGEN/FoQ1QTRA6Aqf+3fLMg4mS4oyTfw5xyYNbv3qoyLPrp+BnxI53WB9p0hfMg4n9FD6NntBxjDq+Q3Lk/bjC/Y4MaRWdzbMzF9a0lgGfcw9DURlK5p7uGJC9vg34feNoQprxVEZRQ01cHLeob6eGkYm4HxSRx8JY39Mh+9wzJo+k/aIvFleNC3e35NOrkXr6wb5e42n2DwBdPqdNolTLtLFRglAL1LTpp27UjvjieWJAKfoDTR5CKl01sZqt0wPdLLcvsMj6CiPFmccUIOYeZMe86kLBD61Qa5F1EwkgO3Om2qSjW96FzL4skRc+BmU5RrHlAFSldR1wpUgtkUMv9vH5Cy+UJdcvpZ8KbmhZ2PsjF7ddJ1ve9RAw3cP325AyIMwZ77Ef1mgTM0NJze6eSW1qKlEsgt1FADPyeUu1NQTA2H2dueMPGlArWTSUgyWR9AdfpqouT7eg0JWI5w+yUZZC+/rPglYbt84oLmYpwuli0z8FyEQRPIc3EtkfWIv/yYgDr2TZ0N2KvGfpi/MAUWgxI1gleC2uKgEOEtuJthd3XZjF2NoE7IBqjQOINybcJOjyeB5vRLDY1FLuxYzdg1y1etkV4XQig/vje + +install: + # Info for debugging. + - echo %PATH% + - go version + - go env + - go get -v -d -t ./... + + +# Provide a build script, or AppVeyor will call msbuild. +build_script: + - go install -v ./... + - echo %KEYFILE_CONTENTS% > %GCLOUD_TESTS_GOLANG_KEY% + +test_script: + - go test -v ./... diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/authexample_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/authexample_test.go new file mode 100644 index 000000000..fe75467f9 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/authexample_test.go @@ -0,0 +1,49 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloud_test + +import ( + "cloud.google.com/go/datastore" + "golang.org/x/net/context" + "google.golang.org/api/option" +) + +func Example_applicationDefaultCredentials() { + // Google Application Default Credentials is the recommended way to authorize + // and authenticate clients. + // + // See the following link on how to create and obtain Application Default Credentials: + // https://developers.google.com/identity/protocols/application-default-credentials. + client, err := datastore.NewClient(context.Background(), "project-id") + if err != nil { + // TODO: handle error. + } + _ = client // Use the client. +} + +func Example_serviceAccountFile() { + // Use a JSON key file associated with a Google service account to + // authenticate and authorize. Service Account keys can be created and + // downloaded from https://console.developers.google.com/permissions/serviceaccounts. + // + // Note: This example uses the datastore client, but the same steps apply to + // the other client libraries underneath this package. + client, err := datastore.NewClient(context.Background(), + "project-id", option.WithServiceAccountFile("/path/to/service-account-key.json")) + if err != nil { + // TODO: handle error. + } + _ = client // Use the client. +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/cloud.go b/examples/servers/reading-list/vendor/cloud.google.com/go/cloud.go new file mode 100644 index 000000000..6ba428dc6 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/cloud.go @@ -0,0 +1,20 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package cloud is the root of the packages used to access Google Cloud +// Services. See https://godoc.org/cloud.google.com/go for a full list +// of sub-packages. +// +// This package documents how to authorize and authenticate the sub packages. +package cloud // import "cloud.google.com/go" diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata.go b/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata.go new file mode 100644 index 000000000..e708c031b --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata.go @@ -0,0 +1,437 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package metadata provides access to Google Compute Engine (GCE) +// metadata and API service accounts. +// +// This package is a wrapper around the GCE metadata service, +// as documented at https://developers.google.com/compute/docs/metadata. +package metadata // import "cloud.google.com/go/compute/metadata" + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "runtime" + "strings" + "sync" + "time" + + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +const ( + // metadataIP is the documented metadata server IP address. + metadataIP = "169.254.169.254" + + // metadataHostEnv is the environment variable specifying the + // GCE metadata hostname. If empty, the default value of + // metadataIP ("169.254.169.254") is used instead. + // This is variable name is not defined by any spec, as far as + // I know; it was made up for the Go package. + metadataHostEnv = "GCE_METADATA_HOST" + + userAgent = "gcloud-golang/0.1" +) + +type cachedValue struct { + k string + trim bool + mu sync.Mutex + v string +} + +var ( + projID = &cachedValue{k: "project/project-id", trim: true} + projNum = &cachedValue{k: "project/numeric-project-id", trim: true} + instID = &cachedValue{k: "instance/id", trim: true} +) + +var ( + metaClient = &http.Client{ + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 2 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + ResponseHeaderTimeout: 2 * time.Second, + }, + } + subscribeClient = &http.Client{ + Transport: &http.Transport{ + Dial: (&net.Dialer{ + Timeout: 2 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + }, + } +) + +// NotDefinedError is returned when requested metadata is not defined. +// +// The underlying string is the suffix after "/computeMetadata/v1/". +// +// This error is not returned if the value is defined to be the empty +// string. +type NotDefinedError string + +func (suffix NotDefinedError) Error() string { + return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) +} + +// Get returns a value from the metadata service. +// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". +// +// If the GCE_METADATA_HOST environment variable is not defined, a default of +// 169.254.169.254 will be used instead. +// +// If the requested metadata is not defined, the returned error will +// be of type NotDefinedError. +func Get(suffix string) (string, error) { + val, _, err := getETag(metaClient, suffix) + return val, err +} + +// getETag returns a value from the metadata service as well as the associated +// ETag using the provided client. This func is otherwise equivalent to Get. +func getETag(client *http.Client, suffix string) (value, etag string, err error) { + // Using a fixed IP makes it very difficult to spoof the metadata service in + // a container, which is an important use-case for local testing of cloud + // deployments. To enable spoofing of the metadata service, the environment + // variable GCE_METADATA_HOST is first inspected to decide where metadata + // requests shall go. + host := os.Getenv(metadataHostEnv) + if host == "" { + // Using 169.254.169.254 instead of "metadata" here because Go + // binaries built with the "netgo" tag and without cgo won't + // know the search suffix for "metadata" is + // ".google.internal", and this IP address is documented as + // being stable anyway. + host = metadataIP + } + url := "http://" + host + "/computeMetadata/v1/" + suffix + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Metadata-Flavor", "Google") + req.Header.Set("User-Agent", userAgent) + res, err := client.Do(req) + if err != nil { + return "", "", err + } + defer res.Body.Close() + if res.StatusCode == http.StatusNotFound { + return "", "", NotDefinedError(suffix) + } + if res.StatusCode != 200 { + return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url) + } + all, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", err + } + return string(all), res.Header.Get("Etag"), nil +} + +func getTrimmed(suffix string) (s string, err error) { + s, err = Get(suffix) + s = strings.TrimSpace(s) + return +} + +func (c *cachedValue) get() (v string, err error) { + defer c.mu.Unlock() + c.mu.Lock() + if c.v != "" { + return c.v, nil + } + if c.trim { + v, err = getTrimmed(c.k) + } else { + v, err = Get(c.k) + } + if err == nil { + c.v = v + } + return +} + +var ( + onGCEOnce sync.Once + onGCE bool +) + +// OnGCE reports whether this process is running on Google Compute Engine. +func OnGCE() bool { + onGCEOnce.Do(initOnGCE) + return onGCE +} + +func initOnGCE() { + onGCE = testOnGCE() +} + +func testOnGCE() bool { + // The user explicitly said they're on GCE, so trust them. + if os.Getenv(metadataHostEnv) != "" { + return true + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + resc := make(chan bool, 2) + + // Try two strategies in parallel. + // See https://github.com/GoogleCloudPlatform/google-cloud-go/issues/194 + go func() { + req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) + req.Header.Set("User-Agent", userAgent) + res, err := ctxhttp.Do(ctx, metaClient, req) + if err != nil { + resc <- false + return + } + defer res.Body.Close() + resc <- res.Header.Get("Metadata-Flavor") == "Google" + }() + + go func() { + addrs, err := net.LookupHost("metadata.google.internal") + if err != nil || len(addrs) == 0 { + resc <- false + return + } + resc <- strsContains(addrs, metadataIP) + }() + + tryHarder := systemInfoSuggestsGCE() + if tryHarder { + res := <-resc + if res { + // The first strategy succeeded, so let's use it. + return true + } + // Wait for either the DNS or metadata server probe to + // contradict the other one and say we are running on + // GCE. Give it a lot of time to do so, since the system + // info already suggests we're running on a GCE BIOS. + timer := time.NewTimer(5 * time.Second) + defer timer.Stop() + select { + case res = <-resc: + return res + case <-timer.C: + // Too slow. Who knows what this system is. + return false + } + } + + // There's no hint from the system info that we're running on + // GCE, so use the first probe's result as truth, whether it's + // true or false. The goal here is to optimize for speed for + // users who are NOT running on GCE. We can't assume that + // either a DNS lookup or an HTTP request to a blackholed IP + // address is fast. Worst case this should return when the + // metaClient's Transport.ResponseHeaderTimeout or + // Transport.Dial.Timeout fires (in two seconds). + return <-resc +} + +// systemInfoSuggestsGCE reports whether the local system (without +// doing network requests) suggests that we're running on GCE. If this +// returns true, testOnGCE tries a bit harder to reach its metadata +// server. +func systemInfoSuggestsGCE() bool { + if runtime.GOOS != "linux" { + // We don't have any non-Linux clues available, at least yet. + return false + } + slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") + name := strings.TrimSpace(string(slurp)) + return name == "Google" || name == "Google Compute Engine" +} + +// Subscribe subscribes to a value from the metadata service. +// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/". +// The suffix may contain query parameters. +// +// Subscribe calls fn with the latest metadata value indicated by the provided +// suffix. If the metadata value is deleted, fn is called with the empty string +// and ok false. Subscribe blocks until fn returns a non-nil error or the value +// is deleted. Subscribe returns the error value returned from the last call to +// fn, which may be nil when ok == false. +func Subscribe(suffix string, fn func(v string, ok bool) error) error { + const failedSubscribeSleep = time.Second * 5 + + // First check to see if the metadata value exists at all. + val, lastETag, err := getETag(subscribeClient, suffix) + if err != nil { + return err + } + + if err := fn(val, true); err != nil { + return err + } + + ok := true + if strings.ContainsRune(suffix, '?') { + suffix += "&wait_for_change=true&last_etag=" + } else { + suffix += "?wait_for_change=true&last_etag=" + } + for { + val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag)) + if err != nil { + if _, deleted := err.(NotDefinedError); !deleted { + time.Sleep(failedSubscribeSleep) + continue // Retry on other errors. + } + ok = false + } + lastETag = etag + + if err := fn(val, ok); err != nil || !ok { + return err + } + } +} + +// ProjectID returns the current instance's project ID string. +func ProjectID() (string, error) { return projID.get() } + +// NumericProjectID returns the current instance's numeric project ID. +func NumericProjectID() (string, error) { return projNum.get() } + +// InternalIP returns the instance's primary internal IP address. +func InternalIP() (string, error) { + return getTrimmed("instance/network-interfaces/0/ip") +} + +// ExternalIP returns the instance's primary external (public) IP address. +func ExternalIP() (string, error) { + return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") +} + +// Hostname returns the instance's hostname. This will be of the form +// ".c..internal". +func Hostname() (string, error) { + return getTrimmed("instance/hostname") +} + +// InstanceTags returns the list of user-defined instance tags, +// assigned when initially creating a GCE instance. +func InstanceTags() ([]string, error) { + var s []string + j, err := Get("instance/tags") + if err != nil { + return nil, err + } + if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { + return nil, err + } + return s, nil +} + +// InstanceID returns the current VM's numeric instance ID. +func InstanceID() (string, error) { + return instID.get() +} + +// InstanceName returns the current VM's instance ID string. +func InstanceName() (string, error) { + host, err := Hostname() + if err != nil { + return "", err + } + return strings.Split(host, ".")[0], nil +} + +// Zone returns the current VM's zone, such as "us-central1-b". +func Zone() (string, error) { + zone, err := getTrimmed("instance/zone") + // zone is of the form "projects//zones/". + if err != nil { + return "", err + } + return zone[strings.LastIndex(zone, "/")+1:], nil +} + +// InstanceAttributes returns the list of user-defined attributes, +// assigned when initially creating a GCE VM instance. The value of an +// attribute can be obtained with InstanceAttributeValue. +func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") } + +// ProjectAttributes returns the list of user-defined attributes +// applying to the project as a whole, not just this VM. The value of +// an attribute can be obtained with ProjectAttributeValue. +func ProjectAttributes() ([]string, error) { return lines("project/attributes/") } + +func lines(suffix string) ([]string, error) { + j, err := Get(suffix) + if err != nil { + return nil, err + } + s := strings.Split(strings.TrimSpace(j), "\n") + for i := range s { + s[i] = strings.TrimSpace(s[i]) + } + return s, nil +} + +// InstanceAttributeValue returns the value of the provided VM +// instance attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// InstanceAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func InstanceAttributeValue(attr string) (string, error) { + return Get("instance/attributes/" + attr) +} + +// ProjectAttributeValue returns the value of the provided +// project attribute. +// +// If the requested attribute is not defined, the returned error will +// be of type NotDefinedError. +// +// ProjectAttributeValue may return ("", nil) if the attribute was +// defined to be the empty string. +func ProjectAttributeValue(attr string) (string, error) { + return Get("project/attributes/" + attr) +} + +// Scopes returns the service account scopes for the given account. +// The account may be empty or the string "default" to use the instance's +// main account. +func Scopes(serviceAccount string) ([]string, error) { + if serviceAccount == "" { + serviceAccount = "default" + } + return lines("instance/service-accounts/" + serviceAccount + "/scopes") +} + +func strsContains(ss []string, s string) bool { + for _, v := range ss { + if v == s { + return true + } + } + return false +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata_test.go new file mode 100644 index 000000000..9ac592691 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/compute/metadata/metadata_test.go @@ -0,0 +1,48 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metadata + +import ( + "os" + "sync" + "testing" +) + +func TestOnGCE_Stress(t *testing.T) { + if testing.Short() { + t.Skip("skipping in -short mode") + } + var last bool + for i := 0; i < 100; i++ { + onGCEOnce = sync.Once{} + + now := OnGCE() + if i > 0 && now != last { + t.Errorf("%d. changed from %v to %v", i, last, now) + } + last = now + } + t.Logf("OnGCE() = %v", last) +} + +func TestOnGCE_Force(t *testing.T) { + onGCEOnce = sync.Once{} + old := os.Getenv(metadataHostEnv) + defer os.Setenv(metadataHostEnv, old) + os.Setenv(metadataHostEnv, "127.0.0.1") + if !OnGCE() { + t.Error("OnGCE() = false; want true") + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore.go new file mode 100644 index 000000000..e9ffbfab7 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore.go @@ -0,0 +1,604 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "errors" + "fmt" + "log" + "os" + "reflect" + + "cloud.google.com/go/internal/version" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/api/option" + "google.golang.org/api/transport" + pb "google.golang.org/genproto/googleapis/datastore/v1" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +const ( + prodAddr = "datastore.googleapis.com:443" + userAgent = "gcloud-golang-datastore/20160401" +) + +// ScopeDatastore grants permissions to view and/or manage datastore entities +const ScopeDatastore = "https://www.googleapis.com/auth/datastore" + +// resourcePrefixHeader is the name of the metadata header used to indicate +// the resource being operated on. +const resourcePrefixHeader = "google-cloud-resource-prefix" + +// protoClient is an interface for *transport.ProtoClient to support injecting +// fake clients in tests. +type protoClient interface { + Call(context.Context, string, proto.Message, proto.Message) error +} + +// datastoreClient is a wrapper for the pb.DatastoreClient that includes gRPC +// metadata to be sent in each request for server-side traffic management. +type datastoreClient struct { + // Embed so we still implement the DatastoreClient interface, + // if the interface adds more methods. + pb.DatastoreClient + + c pb.DatastoreClient + md metadata.MD +} + +func newDatastoreClient(conn *grpc.ClientConn, projectID string) pb.DatastoreClient { + return &datastoreClient{ + c: pb.NewDatastoreClient(conn), + md: metadata.Pairs( + resourcePrefixHeader, "projects/"+projectID, + "x-goog-api-client", fmt.Sprintf("gl-go/%s gccl/%s grpc/", version.Go(), version.Repo)), + } +} + +func (dc *datastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (*pb.LookupResponse, error) { + return dc.c.Lookup(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +func (dc *datastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (*pb.RunQueryResponse, error) { + return dc.c.RunQuery(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +func (dc *datastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (*pb.BeginTransactionResponse, error) { + return dc.c.BeginTransaction(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +func (dc *datastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (*pb.CommitResponse, error) { + return dc.c.Commit(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +func (dc *datastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (*pb.RollbackResponse, error) { + return dc.c.Rollback(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +func (dc *datastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (*pb.AllocateIdsResponse, error) { + return dc.c.AllocateIds(metadata.NewOutgoingContext(ctx, dc.md), in, opts...) +} + +// Client is a client for reading and writing data in a datastore dataset. +type Client struct { + conn *grpc.ClientConn + client pb.DatastoreClient + endpoint string + dataset string // Called dataset by the datastore API, synonym for project ID. +} + +// NewClient creates a new Client for a given dataset. +// If the project ID is empty, it is derived from the DATASTORE_PROJECT_ID environment variable. +// If the DATASTORE_EMULATOR_HOST environment variable is set, client will use its value +// to connect to a locally-running datastore emulator. +func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) { + var o []option.ClientOption + // Environment variables for gcd emulator: + // https://cloud.google.com/datastore/docs/tools/datastore-emulator + // If the emulator is available, dial it directly (and don't pass any credentials). + if addr := os.Getenv("DATASTORE_EMULATOR_HOST"); addr != "" { + conn, err := grpc.Dial(addr, grpc.WithInsecure()) + if err != nil { + return nil, fmt.Errorf("grpc.Dial: %v", err) + } + o = []option.ClientOption{option.WithGRPCConn(conn)} + } else { + o = []option.ClientOption{ + option.WithEndpoint(prodAddr), + option.WithScopes(ScopeDatastore), + option.WithUserAgent(userAgent), + } + } + // Warn if we see the legacy emulator environment variables. + if os.Getenv("DATASTORE_HOST") != "" && os.Getenv("DATASTORE_EMULATOR_HOST") == "" { + log.Print("WARNING: legacy environment variable DATASTORE_HOST is ignored. Use DATASTORE_EMULATOR_HOST instead.") + } + if os.Getenv("DATASTORE_DATASET") != "" && os.Getenv("DATASTORE_PROJECT_ID") == "" { + log.Print("WARNING: legacy environment variable DATASTORE_DATASET is ignored. Use DATASTORE_PROJECT_ID instead.") + } + if projectID == "" { + projectID = os.Getenv("DATASTORE_PROJECT_ID") + } + if projectID == "" { + return nil, errors.New("datastore: missing project/dataset id") + } + o = append(o, opts...) + conn, err := transport.DialGRPC(ctx, o...) + if err != nil { + return nil, fmt.Errorf("dialing: %v", err) + } + return &Client{ + conn: conn, + client: newDatastoreClient(conn, projectID), + dataset: projectID, + }, nil + +} + +var ( + // ErrInvalidEntityType is returned when functions like Get or Next are + // passed a dst or src argument of invalid type. + ErrInvalidEntityType = errors.New("datastore: invalid entity type") + // ErrInvalidKey is returned when an invalid key is presented. + ErrInvalidKey = errors.New("datastore: invalid key") + // ErrNoSuchEntity is returned when no entity was found for a given key. + ErrNoSuchEntity = errors.New("datastore: no such entity") +) + +type multiArgType int + +const ( + multiArgTypeInvalid multiArgType = iota + multiArgTypePropertyLoadSaver + multiArgTypeStruct + multiArgTypeStructPtr + multiArgTypeInterface +) + +// ErrFieldMismatch is returned when a field is to be loaded into a different +// type than the one it was stored from, or when a field is missing or +// unexported in the destination struct. +// StructType is the type of the struct pointed to by the destination argument +// passed to Get or to Iterator.Next. +type ErrFieldMismatch struct { + StructType reflect.Type + FieldName string + Reason string +} + +func (e *ErrFieldMismatch) Error() string { + return fmt.Sprintf("datastore: cannot load field %q into a %q: %s", + e.FieldName, e.StructType, e.Reason) +} + +// GeoPoint represents a location as latitude/longitude in degrees. +type GeoPoint struct { + Lat, Lng float64 +} + +// Valid returns whether a GeoPoint is within [-90, 90] latitude and [-180, 180] longitude. +func (g GeoPoint) Valid() bool { + return -90 <= g.Lat && g.Lat <= 90 && -180 <= g.Lng && g.Lng <= 180 +} + +func keyToProto(k *Key) *pb.Key { + if k == nil { + return nil + } + + // TODO(jbd): Eliminate unrequired allocations. + var path []*pb.Key_PathElement + for { + el := &pb.Key_PathElement{Kind: k.Kind} + if k.ID != 0 { + el.IdType = &pb.Key_PathElement_Id{Id: k.ID} + } else if k.Name != "" { + el.IdType = &pb.Key_PathElement_Name{Name: k.Name} + } + path = append([]*pb.Key_PathElement{el}, path...) + if k.Parent == nil { + break + } + k = k.Parent + } + key := &pb.Key{Path: path} + if k.Namespace != "" { + key.PartitionId = &pb.PartitionId{ + NamespaceId: k.Namespace, + } + } + return key +} + +// protoToKey decodes a protocol buffer representation of a key into an +// equivalent *Key object. If the key is invalid, protoToKey will return the +// invalid key along with ErrInvalidKey. +func protoToKey(p *pb.Key) (*Key, error) { + var key *Key + var namespace string + if partition := p.PartitionId; partition != nil { + namespace = partition.NamespaceId + } + for _, el := range p.Path { + key = &Key{ + Namespace: namespace, + Kind: el.Kind, + ID: el.GetId(), + Name: el.GetName(), + Parent: key, + } + } + if !key.valid() { // Also detects key == nil. + return key, ErrInvalidKey + } + return key, nil +} + +// multiKeyToProto is a batch version of keyToProto. +func multiKeyToProto(keys []*Key) []*pb.Key { + ret := make([]*pb.Key, len(keys)) + for i, k := range keys { + ret[i] = keyToProto(k) + } + return ret +} + +// multiKeyToProto is a batch version of keyToProto. +func multiProtoToKey(keys []*pb.Key) ([]*Key, error) { + hasErr := false + ret := make([]*Key, len(keys)) + err := make(MultiError, len(keys)) + for i, k := range keys { + ret[i], err[i] = protoToKey(k) + if err[i] != nil { + hasErr = true + } + } + if hasErr { + return nil, err + } + return ret, nil +} + +// multiValid is a batch version of Key.valid. It returns an error, not a +// []bool. +func multiValid(key []*Key) error { + invalid := false + for _, k := range key { + if !k.valid() { + invalid = true + break + } + } + if !invalid { + return nil + } + err := make(MultiError, len(key)) + for i, k := range key { + if !k.valid() { + err[i] = ErrInvalidKey + } + } + return err +} + +// checkMultiArg checks that v has type []S, []*S, []I, or []P, for some struct +// type S, for some interface type I, or some non-interface non-pointer type P +// such that P or *P implements PropertyLoadSaver. +// +// It returns what category the slice's elements are, and the reflect.Type +// that represents S, I or P. +// +// As a special case, PropertyList is an invalid type for v. +// +// TODO(djd): multiArg is very confusing. Fold this logic into the +// relevant Put/Get methods to make the logic less opaque. +func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) { + if v.Kind() != reflect.Slice { + return multiArgTypeInvalid, nil + } + if v.Type() == typeOfPropertyList { + return multiArgTypeInvalid, nil + } + elemType = v.Type().Elem() + if reflect.PtrTo(elemType).Implements(typeOfPropertyLoadSaver) { + return multiArgTypePropertyLoadSaver, elemType + } + switch elemType.Kind() { + case reflect.Struct: + return multiArgTypeStruct, elemType + case reflect.Interface: + return multiArgTypeInterface, elemType + case reflect.Ptr: + elemType = elemType.Elem() + if elemType.Kind() == reflect.Struct { + return multiArgTypeStructPtr, elemType + } + } + return multiArgTypeInvalid, nil +} + +// Close closes the Client. +func (c *Client) Close() error { + return c.conn.Close() +} + +// Get loads the entity stored for key into dst, which must be a struct pointer +// or implement PropertyLoadSaver. If there is no such entity for the key, Get +// returns ErrNoSuchEntity. +// +// The values of dst's unmatched struct fields are not modified, and matching +// slice-typed fields are not reset before appending to them. In particular, it +// is recommended to pass a pointer to a zero valued struct on each Get call. +// +// ErrFieldMismatch is returned when a field is to be loaded into a different +// type than the one it was stored from, or when a field is missing or +// unexported in the destination struct. ErrFieldMismatch is only returned if +// dst is a struct pointer. +func (c *Client) Get(ctx context.Context, key *Key, dst interface{}) error { + if dst == nil { // get catches nil interfaces; we need to catch nil ptr here + return ErrInvalidEntityType + } + err := c.get(ctx, []*Key{key}, []interface{}{dst}, nil) + if me, ok := err.(MultiError); ok { + return me[0] + } + return err +} + +// GetMulti is a batch version of Get. +// +// dst must be a []S, []*S, []I or []P, for some struct type S, some interface +// type I, or some non-interface non-pointer type P such that P or *P +// implements PropertyLoadSaver. If an []I, each element must be a valid dst +// for Get: it must be a struct pointer or implement PropertyLoadSaver. +// +// As a special case, PropertyList is an invalid type for dst, even though a +// PropertyList is a slice of structs. It is treated as invalid to avoid being +// mistakenly passed when []PropertyList was intended. +func (c *Client) GetMulti(ctx context.Context, keys []*Key, dst interface{}) error { + return c.get(ctx, keys, dst, nil) +} + +func (c *Client) get(ctx context.Context, keys []*Key, dst interface{}, opts *pb.ReadOptions) error { + v := reflect.ValueOf(dst) + multiArgType, _ := checkMultiArg(v) + + // Sanity checks + if multiArgType == multiArgTypeInvalid { + return errors.New("datastore: dst has invalid type") + } + if len(keys) != v.Len() { + return errors.New("datastore: keys and dst slices have different length") + } + if len(keys) == 0 { + return nil + } + + // Go through keys, validate them, serialize then, and create a dict mapping them to their index + multiErr, any := make(MultiError, len(keys)), false + keyMap := make(map[string]int) + pbKeys := make([]*pb.Key, len(keys)) + for i, k := range keys { + if !k.valid() { + multiErr[i] = ErrInvalidKey + any = true + } else { + keyMap[k.String()] = i + pbKeys[i] = keyToProto(k) + } + } + if any { + return multiErr + } + req := &pb.LookupRequest{ + ProjectId: c.dataset, + Keys: pbKeys, + ReadOptions: opts, + } + resp, err := c.client.Lookup(ctx, req) + if err != nil { + return err + } + found := resp.Found + missing := resp.Missing + // Upper bound 100 iterations to prevent infinite loop. + // We choose 100 iterations somewhat logically: + // Max number of Entities you can request from Datastore is 1,000. + // Max size for a Datastore Entity is 1 MiB. + // Max request size is 10 MiB, so we assume max response size is also 10 MiB. + // 1,000 / 10 = 100. + // Note that if ctx has a deadline, the deadline will probably + // be hit before we reach 100 iterations. + for i := 0; len(resp.Deferred) > 0 && i < 100; i++ { + req.Keys = resp.Deferred + resp, err = c.client.Lookup(ctx, req) + if err != nil { + return err + } + found = append(found, resp.Found...) + missing = append(missing, resp.Missing...) + } + if len(keys) != len(found)+len(missing) { + return errors.New("datastore: internal error: server returned the wrong number of entities") + } + for _, e := range found { + k, err := protoToKey(e.Entity.Key) + if err != nil { + return errors.New("datastore: internal error: server returned an invalid key") + } + index := keyMap[k.String()] + elem := v.Index(index) + if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { + elem = elem.Addr() + } + if multiArgType == multiArgTypeStructPtr && elem.IsNil() { + elem.Set(reflect.New(elem.Type().Elem())) + } + if err := loadEntityProto(elem.Interface(), e.Entity); err != nil { + multiErr[index] = err + any = true + } + } + for _, e := range missing { + k, err := protoToKey(e.Entity.Key) + if err != nil { + return errors.New("datastore: internal error: server returned an invalid key") + } + multiErr[keyMap[k.String()]] = ErrNoSuchEntity + any = true + } + if any { + return multiErr + } + return nil +} + +// Put saves the entity src into the datastore with key k. src must be a struct +// pointer or implement PropertyLoadSaver; if a struct pointer then any +// unexported fields of that struct will be skipped. If k is an incomplete key, +// the returned key will be a unique key generated by the datastore. +func (c *Client) Put(ctx context.Context, key *Key, src interface{}) (*Key, error) { + k, err := c.PutMulti(ctx, []*Key{key}, []interface{}{src}) + if err != nil { + if me, ok := err.(MultiError); ok { + return nil, me[0] + } + return nil, err + } + return k[0], nil +} + +// PutMulti is a batch version of Put. +// +// src must satisfy the same conditions as the dst argument to GetMulti. +func (c *Client) PutMulti(ctx context.Context, keys []*Key, src interface{}) ([]*Key, error) { + mutations, err := putMutations(keys, src) + if err != nil { + return nil, err + } + + // Make the request. + req := &pb.CommitRequest{ + ProjectId: c.dataset, + Mutations: mutations, + Mode: pb.CommitRequest_NON_TRANSACTIONAL, + } + resp, err := c.client.Commit(ctx, req) + if err != nil { + return nil, err + } + + // Copy any newly minted keys into the returned keys. + ret := make([]*Key, len(keys)) + for i, key := range keys { + if key.Incomplete() { + // This key is in the mutation results. + ret[i], err = protoToKey(resp.MutationResults[i].Key) + if err != nil { + return nil, errors.New("datastore: internal error: server returned an invalid key") + } + } else { + ret[i] = key + } + } + return ret, nil +} + +func putMutations(keys []*Key, src interface{}) ([]*pb.Mutation, error) { + v := reflect.ValueOf(src) + multiArgType, _ := checkMultiArg(v) + if multiArgType == multiArgTypeInvalid { + return nil, errors.New("datastore: src has invalid type") + } + if len(keys) != v.Len() { + return nil, errors.New("datastore: key and src slices have different length") + } + if len(keys) == 0 { + return nil, nil + } + if err := multiValid(keys); err != nil { + return nil, err + } + mutations := make([]*pb.Mutation, 0, len(keys)) + multiErr := make(MultiError, len(keys)) + hasErr := false + for i, k := range keys { + elem := v.Index(i) + // Two cases where we need to take the address: + // 1) multiArgTypePropertyLoadSaver => &elem implements PLS + // 2) multiArgTypeStruct => saveEntity needs *struct + if multiArgType == multiArgTypePropertyLoadSaver || multiArgType == multiArgTypeStruct { + elem = elem.Addr() + } + p, err := saveEntity(k, elem.Interface()) + if err != nil { + multiErr[i] = err + hasErr = true + } + var mut *pb.Mutation + if k.Incomplete() { + mut = &pb.Mutation{Operation: &pb.Mutation_Insert{Insert: p}} + } else { + mut = &pb.Mutation{Operation: &pb.Mutation_Upsert{Upsert: p}} + } + mutations = append(mutations, mut) + } + if hasErr { + return nil, multiErr + } + return mutations, nil +} + +// Delete deletes the entity for the given key. +func (c *Client) Delete(ctx context.Context, key *Key) error { + err := c.DeleteMulti(ctx, []*Key{key}) + if me, ok := err.(MultiError); ok { + return me[0] + } + return err +} + +// DeleteMulti is a batch version of Delete. +func (c *Client) DeleteMulti(ctx context.Context, keys []*Key) error { + mutations, err := deleteMutations(keys) + if err != nil { + return err + } + + req := &pb.CommitRequest{ + ProjectId: c.dataset, + Mutations: mutations, + Mode: pb.CommitRequest_NON_TRANSACTIONAL, + } + _, err = c.client.Commit(ctx, req) + return err +} + +func deleteMutations(keys []*Key) ([]*pb.Mutation, error) { + mutations := make([]*pb.Mutation, 0, len(keys)) + for _, k := range keys { + if k.Incomplete() { + return nil, fmt.Errorf("datastore: can't delete the incomplete key: %v", k) + } + mutations = append(mutations, &pb.Mutation{ + Operation: &pb.Mutation_Delete{Delete: keyToProto(k)}, + }) + } + return mutations, nil +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore_test.go new file mode 100644 index 000000000..3333a665e --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/datastore_test.go @@ -0,0 +1,3463 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "sort" + "strings" + "testing" + "time" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + pb "google.golang.org/genproto/googleapis/datastore/v1" + "google.golang.org/grpc" +) + +type ( + myBlob []byte + myByte byte + myString string +) + +func makeMyByteSlice(n int) []myByte { + b := make([]myByte, n) + for i := range b { + b[i] = myByte(i) + } + return b +} + +func makeInt8Slice(n int) []int8 { + b := make([]int8, n) + for i := range b { + b[i] = int8(i) + } + return b +} + +func makeUint8Slice(n int) []uint8 { + b := make([]uint8, n) + for i := range b { + b[i] = uint8(i) + } + return b +} + +func newKey(stringID string, parent *Key) *Key { + return NameKey("kind", stringID, parent) +} + +var ( + testKey0 = newKey("name0", nil) + testKey1a = newKey("name1", nil) + testKey1b = newKey("name1", nil) + testKey2a = newKey("name2", testKey0) + testKey2b = newKey("name2", testKey0) + testGeoPt0 = GeoPoint{Lat: 1.2, Lng: 3.4} + testGeoPt1 = GeoPoint{Lat: 5, Lng: 10} + testBadGeoPt = GeoPoint{Lat: 1000, Lng: 34} + + ts = time.Unix(1e9, 0).UTC() +) + +type B0 struct { + B []byte `datastore:",noindex"` +} + +type B1 struct { + B []int8 +} + +type B2 struct { + B myBlob `datastore:",noindex"` +} + +type B3 struct { + B []myByte `datastore:",noindex"` +} + +type B4 struct { + B [][]byte +} + +type C0 struct { + I int + C chan int +} + +type C1 struct { + I int + C *chan int +} + +type C2 struct { + I int + C []chan int +} + +type C3 struct { + C string +} + +type c4 struct { + C string +} + +type E struct{} + +type G0 struct { + G GeoPoint +} + +type G1 struct { + G []GeoPoint +} + +type K0 struct { + K *Key +} + +type K1 struct { + K []*Key +} + +type S struct { + St string +} + +type NoOmit struct { + A string + B int `datastore:"Bb"` + C bool `datastore:",noindex"` +} + +type OmitAll struct { + A string `datastore:",omitempty"` + B int `datastore:"Bb,omitempty"` + C bool `datastore:",omitempty,noindex"` + F []int `datastore:",omitempty"` +} + +type Omit struct { + A string `datastore:",omitempty"` + B int `datastore:"Bb,omitempty"` + C bool `datastore:",omitempty,noindex"` + F []int `datastore:",omitempty"` + S `datastore:",omitempty"` +} + +type NoOmits struct { + No []NoOmit `datastore:",omitempty"` + S `datastore:",omitempty"` + Ss S `datastore:",omitempty"` +} + +type N0 struct { + X0 + Nonymous X0 + Ignore string `datastore:"-"` + Other string +} + +type N1 struct { + X0 + Nonymous []X0 + Ignore string `datastore:"-"` + Other string +} + +type N2 struct { + N1 `datastore:"red"` + Green N1 `datastore:"green"` + Blue N1 + White N1 `datastore:"-"` +} + +type N3 struct { + C3 `datastore:"red"` +} + +type N4 struct { + c4 +} + +type N5 struct { + c4 `datastore:"red"` +} + +type O0 struct { + I int64 +} + +type O1 struct { + I int32 +} + +type U0 struct { + U uint +} + +type U1 struct { + U string +} + +type T struct { + T time.Time +} + +type X0 struct { + S string + I int + i int +} + +type X1 struct { + S myString + I int32 + J int64 +} + +type X2 struct { + Z string + i int +} + +type X3 struct { + S bool + I int +} + +type Y0 struct { + B bool + F []float64 + G []float64 +} + +type Y1 struct { + B bool + F float64 +} + +type Y2 struct { + B bool + F []int64 +} + +type Tagged struct { + A int `datastore:"a,noindex"` + B []int `datastore:"b"` + C int `datastore:",noindex"` + D int `datastore:""` + E int + I int `datastore:"-"` + J int `datastore:",noindex" json:"j"` + + Y0 `datastore:"-"` + Z chan int `datastore:"-"` +} + +type InvalidTagged1 struct { + I int `datastore:"\t"` +} + +type InvalidTagged2 struct { + I int + J int `datastore:"I"` +} + +type InvalidTagged3 struct { + X string `datastore:"-,noindex"` +} + +type InvalidTagged4 struct { + X string `datastore:",garbage"` +} + +type Inner1 struct { + W int32 + X string +} + +type Inner2 struct { + Y float64 +} + +type Inner3 struct { + Z bool +} + +type Inner5 struct { + WW int +} + +type Inner4 struct { + X Inner5 +} + +type Outer struct { + A int16 + I []Inner1 + J Inner2 + Inner3 +} + +type OuterFlatten struct { + A int16 + I []Inner1 `datastore:",flatten"` + J Inner2 `datastore:",flatten,noindex"` + Inner3 `datastore:",flatten"` + K Inner4 `datastore:",flatten"` +} + +type OuterEquivalent struct { + A int16 + IDotW []int32 `datastore:"I.W"` + IDotX []string `datastore:"I.X"` + JDotY float64 `datastore:"J.Y"` + Z bool +} + +type Dotted struct { + A DottedA `datastore:"A0.A1.A2"` +} + +type DottedA struct { + B DottedB `datastore:"B3"` +} + +type DottedB struct { + C int `datastore:"C4.C5"` +} + +type SliceOfSlices struct { + I int + S []struct { + J int + F []float64 + } `datastore:",flatten"` +} + +type Recursive struct { + I int + R []Recursive +} + +type MutuallyRecursive0 struct { + I int + R []MutuallyRecursive1 +} + +type MutuallyRecursive1 struct { + I int + R []MutuallyRecursive0 +} + +type EntityWithKey struct { + I int + S string + K *Key `datastore:"__key__"` +} + +type EntityWithKey2 EntityWithKey + +type WithNestedEntityWithKey struct { + N EntityWithKey +} + +type WithNonKeyField struct { + I int + K string `datastore:"__key__"` +} + +type NestedWithNonKeyField struct { + N WithNonKeyField +} + +type Basic struct { + A string +} + +type PtrToStructField struct { + B *Basic + C *Basic `datastore:"c,noindex"` + *Basic + D []*Basic +} + +var two int = 2 + +type PtrToInt struct { + I *int +} + +type EmbeddedTime struct { + time.Time +} + +type SpecialTime struct { + MyTime EmbeddedTime +} + +type Doubler struct { + S string + I int64 + B bool +} + +type Repeat struct { + Key string + Value []byte +} + +type Repeated struct { + Repeats []Repeat +} + +func (d *Doubler) Load(props []Property) error { + return LoadStruct(d, props) +} + +func (d *Doubler) Save() ([]Property, error) { + // Save the default Property slice to an in-memory buffer (a PropertyList). + props, err := SaveStruct(d) + if err != nil { + return nil, err + } + var list PropertyList + if err := list.Load(props); err != nil { + return nil, err + } + + // Edit that PropertyList, and send it on. + for i := range list { + switch v := list[i].Value.(type) { + case string: + // + means string concatenation. + list[i].Value = v + v + case int64: + // + means integer addition. + list[i].Value = v + v + } + } + return list.Save() +} + +var _ PropertyLoadSaver = (*Doubler)(nil) + +type Deriver struct { + S, Derived, Ignored string +} + +func (e *Deriver) Load(props []Property) error { + for _, p := range props { + if p.Name != "S" { + continue + } + e.S = p.Value.(string) + e.Derived = "derived+" + e.S + } + return nil +} + +func (e *Deriver) Save() ([]Property, error) { + return []Property{ + { + Name: "S", + Value: e.S, + }, + }, nil +} + +var _ PropertyLoadSaver = (*Deriver)(nil) + +type BadMultiPropEntity struct{} + +func (e *BadMultiPropEntity) Load(props []Property) error { + return errors.New("unimplemented") +} + +func (e *BadMultiPropEntity) Save() ([]Property, error) { + // Write multiple properties with the same name "I". + var props []Property + for i := 0; i < 3; i++ { + props = append(props, Property{ + Name: "I", + Value: int64(i), + }) + } + return props, nil +} + +var _ PropertyLoadSaver = (*BadMultiPropEntity)(nil) + +type testCase struct { + desc string + src interface{} + want interface{} + putErr string + getErr string +} + +var testCases = []testCase{ + { + "chan save fails", + &C0{I: -1}, + &E{}, + "unsupported struct field", + "", + }, + { + "*chan save fails", + &C1{I: -1}, + &E{}, + "unsupported struct field", + "", + }, + { + "[]chan save fails", + &C2{I: -1, C: make([]chan int, 8)}, + &E{}, + "unsupported struct field", + "", + }, + { + "chan load fails", + &C3{C: "not a chan"}, + &C0{}, + "", + "type mismatch", + }, + { + "*chan load fails", + &C3{C: "not a *chan"}, + &C1{}, + "", + "type mismatch", + }, + { + "[]chan load fails", + &C3{C: "not a []chan"}, + &C2{}, + "", + "type mismatch", + }, + { + "empty struct", + &E{}, + &E{}, + "", + "", + }, + { + "geopoint", + &G0{G: testGeoPt0}, + &G0{G: testGeoPt0}, + "", + "", + }, + { + "geopoint invalid", + &G0{G: testBadGeoPt}, + &G0{}, + "invalid GeoPoint value", + "", + }, + { + "geopoint as props", + &G0{G: testGeoPt0}, + &PropertyList{ + Property{Name: "G", Value: testGeoPt0, NoIndex: false}, + }, + "", + "", + }, + { + "geopoint slice", + &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}}, + &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}}, + "", + "", + }, + { + "omit empty, all", + &OmitAll{}, + new(PropertyList), + "", + "", + }, + { + "omit empty", + &Omit{}, + &PropertyList{ + Property{Name: "St", Value: "", NoIndex: false}, + }, + "", + "", + }, + { + "omit empty, fields populated", + &Omit{ + A: "a", + B: 10, + C: true, + F: []int{11}, + }, + &PropertyList{ + Property{Name: "A", Value: "a", NoIndex: false}, + Property{Name: "Bb", Value: int64(10), NoIndex: false}, + Property{Name: "C", Value: true, NoIndex: true}, + Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false}, + Property{Name: "St", Value: "", NoIndex: false}, + }, + "", + "", + }, + { + "omit empty, fields populated", + &Omit{ + A: "a", + B: 10, + C: true, + F: []int{11}, + S: S{St: "string"}, + }, + &PropertyList{ + Property{Name: "A", Value: "a", NoIndex: false}, + Property{Name: "Bb", Value: int64(10), NoIndex: false}, + Property{Name: "C", Value: true, NoIndex: true}, + Property{Name: "F", Value: []interface{}{int64(11)}, NoIndex: false}, + Property{Name: "St", Value: "string", NoIndex: false}, + }, + "", + "", + }, + { + "omit empty does not propagate", + &NoOmits{ + No: []NoOmit{ + NoOmit{}, + }, + S: S{}, + Ss: S{}, + }, + &PropertyList{ + Property{Name: "No", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "A", Value: "", NoIndex: false}, + Property{Name: "Bb", Value: int64(0), NoIndex: false}, + Property{Name: "C", Value: false, NoIndex: true}, + }, + }, + }, NoIndex: false}, + Property{Name: "Ss", Value: &Entity{ + Properties: []Property{ + Property{Name: "St", Value: "", NoIndex: false}, + }, + }, NoIndex: false}, + Property{Name: "St", Value: "", NoIndex: false}, + }, + "", + "", + }, + { + "key", + &K0{K: testKey1a}, + &K0{K: testKey1b}, + "", + "", + }, + { + "key with parent", + &K0{K: testKey2a}, + &K0{K: testKey2b}, + "", + "", + }, + { + "nil key", + &K0{}, + &K0{}, + "", + "", + }, + { + "all nil keys in slice", + &K1{[]*Key{nil, nil}}, + &K1{[]*Key{nil, nil}}, + "", + "", + }, + { + "some nil keys in slice", + &K1{[]*Key{testKey1a, nil, testKey2a}}, + &K1{[]*Key{testKey1b, nil, testKey2b}}, + "", + "", + }, + { + "overflow", + &O0{I: 1 << 48}, + &O1{}, + "", + "overflow", + }, + { + "time", + &T{T: time.Unix(1e9, 0)}, + &T{T: time.Unix(1e9, 0)}, + "", + "", + }, + { + "time as props", + &T{T: time.Unix(1e9, 0)}, + &PropertyList{ + Property{Name: "T", Value: time.Unix(1e9, 0), NoIndex: false}, + }, + "", + "", + }, + { + "uint save", + &U0{U: 1}, + &U0{}, + "unsupported struct field", + "", + }, + { + "uint load", + &U1{U: "not a uint"}, + &U0{}, + "", + "type mismatch", + }, + { + "zero", + &X0{}, + &X0{}, + "", + "", + }, + { + "basic", + &X0{S: "one", I: 2, i: 3}, + &X0{S: "one", I: 2}, + "", + "", + }, + { + "save string/int load myString/int32", + &X0{S: "one", I: 2, i: 3}, + &X1{S: "one", I: 2}, + "", + "", + }, + { + "missing fields", + &X0{S: "one", I: 2, i: 3}, + &X2{}, + "", + "no such struct field", + }, + { + "save string load bool", + &X0{S: "one", I: 2, i: 3}, + &X3{I: 2}, + "", + "type mismatch", + }, + { + "basic slice", + &Y0{B: true, F: []float64{7, 8, 9}}, + &Y0{B: true, F: []float64{7, 8, 9}}, + "", + "", + }, + { + "save []float64 load float64", + &Y0{B: true, F: []float64{7, 8, 9}}, + &Y1{B: true}, + "", + "requires a slice", + }, + { + "save []float64 load []int64", + &Y0{B: true, F: []float64{7, 8, 9}}, + &Y2{B: true}, + "", + "type mismatch", + }, + { + "single slice is too long", + &Y0{F: make([]float64, maxIndexedProperties+1)}, + &Y0{}, + "too many indexed properties", + "", + }, + { + "two slices are too long", + &Y0{F: make([]float64, maxIndexedProperties), G: make([]float64, maxIndexedProperties)}, + &Y0{}, + "too many indexed properties", + "", + }, + { + "one slice and one scalar are too long", + &Y0{F: make([]float64, maxIndexedProperties), B: true}, + &Y0{}, + "too many indexed properties", + "", + }, + { + "slice of slices of bytes", + &Repeated{ + Repeats: []Repeat{ + { + Key: "key 1", + Value: []byte("value 1"), + }, + { + Key: "key 2", + Value: []byte("value 2"), + }, + }, + }, + &Repeated{ + Repeats: []Repeat{ + { + Key: "key 1", + Value: []byte("value 1"), + }, + { + Key: "key 2", + Value: []byte("value 2"), + }, + }, + }, + "", + "", + }, + { + "long blob", + &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, + &B0{B: makeUint8Slice(maxIndexedProperties + 1)}, + "", + "", + }, + { + "long []int8 is too long", + &B1{B: makeInt8Slice(maxIndexedProperties + 1)}, + &B1{}, + "too many indexed properties", + "", + }, + { + "short []int8", + &B1{B: makeInt8Slice(3)}, + &B1{B: makeInt8Slice(3)}, + "", + "", + }, + { + "long myBlob", + &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, + &B2{B: makeUint8Slice(maxIndexedProperties + 1)}, + "", + "", + }, + { + "short myBlob", + &B2{B: makeUint8Slice(3)}, + &B2{B: makeUint8Slice(3)}, + "", + "", + }, + { + "long []myByte", + &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, + &B3{B: makeMyByteSlice(maxIndexedProperties + 1)}, + "", + "", + }, + { + "short []myByte", + &B3{B: makeMyByteSlice(3)}, + &B3{B: makeMyByteSlice(3)}, + "", + "", + }, + { + "slice of blobs", + &B4{B: [][]byte{ + makeUint8Slice(3), + makeUint8Slice(4), + makeUint8Slice(5), + }}, + &B4{B: [][]byte{ + makeUint8Slice(3), + makeUint8Slice(4), + makeUint8Slice(5), + }}, + "", + "", + }, + { + "[]byte must be noindex", + &PropertyList{ + Property{Name: "B", Value: makeUint8Slice(1501), NoIndex: false}, + }, + nil, + "[]byte property too long to index", + "", + }, + { + "string must be noindex", + &PropertyList{ + Property{Name: "B", Value: strings.Repeat("x", 1501), NoIndex: false}, + }, + nil, + "string property too long to index", + "", + }, + { + "slice of []byte must be noindex", + &PropertyList{ + Property{Name: "B", Value: []interface{}{ + []byte("short"), + makeUint8Slice(1501), + }, NoIndex: false}, + }, + nil, + "[]byte property too long to index", + "", + }, + { + "slice of string must be noindex", + &PropertyList{ + Property{Name: "B", Value: []interface{}{ + "short", + strings.Repeat("x", 1501), + }, NoIndex: false}, + }, + nil, + "string property too long to index", + "", + }, + { + "save tagged load props", + &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7}, + &PropertyList{ + // A and B are renamed to a and b; A and C are noindex, I is ignored. + // Order is sorted as per byName. + Property{Name: "C", Value: int64(3), NoIndex: true}, + Property{Name: "D", Value: int64(4), NoIndex: false}, + Property{Name: "E", Value: int64(5), NoIndex: false}, + Property{Name: "J", Value: int64(7), NoIndex: true}, + Property{Name: "a", Value: int64(1), NoIndex: true}, + Property{Name: "b", Value: []interface{}{int64(21), int64(22), int64(23)}, NoIndex: false}, + }, + "", + "", + }, + { + "save tagged load tagged", + &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6, J: 7}, + &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7}, + "", + "", + }, + { + "invalid tagged1", + &InvalidTagged1{I: 1}, + &InvalidTagged1{}, + "struct tag has invalid property name", + "", + }, + { + "invalid tagged2", + &InvalidTagged2{I: 1, J: 2}, + &InvalidTagged2{J: 2}, + "", + "", + }, + { + "invalid tagged3", + &InvalidTagged3{X: "hello"}, + &InvalidTagged3{}, + "struct tag has invalid property name: \"-\"", + "", + }, + { + "invalid tagged4", + &InvalidTagged4{X: "hello"}, + &InvalidTagged4{}, + "struct tag has invalid option: \"garbage\"", + "", + }, + { + "doubler", + &Doubler{S: "s", I: 1, B: true}, + &Doubler{S: "ss", I: 2, B: true}, + "", + "", + }, + { + "save struct load props", + &X0{S: "s", I: 1}, + &PropertyList{ + Property{Name: "I", Value: int64(1), NoIndex: false}, + Property{Name: "S", Value: "s", NoIndex: false}, + }, + "", + "", + }, + { + "save props load struct", + &PropertyList{ + Property{Name: "I", Value: int64(1), NoIndex: false}, + Property{Name: "S", Value: "s", NoIndex: false}, + }, + &X0{S: "s", I: 1}, + "", + "", + }, + { + "nil-value props", + &PropertyList{ + Property{Name: "I", Value: nil, NoIndex: false}, + Property{Name: "B", Value: nil, NoIndex: false}, + Property{Name: "S", Value: nil, NoIndex: false}, + Property{Name: "F", Value: nil, NoIndex: false}, + Property{Name: "K", Value: nil, NoIndex: false}, + Property{Name: "T", Value: nil, NoIndex: false}, + Property{Name: "J", Value: []interface{}{nil, int64(7), nil}, NoIndex: false}, + }, + &struct { + I int64 + B bool + S string + F float64 + K *Key + T time.Time + J []int64 + }{ + J: []int64{0, 7, 0}, + }, + "", + "", + }, + { + "save outer load props flatten", + &OuterFlatten{ + A: 1, + I: []Inner1{ + {10, "ten"}, + {20, "twenty"}, + {30, "thirty"}, + }, + J: Inner2{ + Y: 3.14, + }, + Inner3: Inner3{ + Z: true, + }, + K: Inner4{ + X: Inner5{ + WW: 12, + }, + }, + }, + &PropertyList{ + Property{Name: "A", Value: int64(1), NoIndex: false}, + Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, + Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, + Property{Name: "J.Y", Value: float64(3.14), NoIndex: true}, + Property{Name: "K.X.WW", Value: int64(12), NoIndex: false}, + Property{Name: "Z", Value: true, NoIndex: false}, + }, + "", + "", + }, + { + "load outer props flatten", + &PropertyList{ + Property{Name: "A", Value: int64(1), NoIndex: false}, + Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, + Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, + Property{Name: "J.Y", Value: float64(3.14), NoIndex: true}, + Property{Name: "Z", Value: true, NoIndex: false}, + }, + &OuterFlatten{ + A: 1, + I: []Inner1{ + {10, "ten"}, + {20, "twenty"}, + {30, "thirty"}, + }, + J: Inner2{ + Y: 3.14, + }, + Inner3: Inner3{ + Z: true, + }, + }, + "", + "", + }, + { + "save outer load props", + &Outer{ + A: 1, + I: []Inner1{ + {10, "ten"}, + {20, "twenty"}, + {30, "thirty"}, + }, + J: Inner2{ + Y: 3.14, + }, + Inner3: Inner3{ + Z: true, + }, + }, + &PropertyList{ + Property{Name: "A", Value: int64(1), NoIndex: false}, + Property{Name: "I", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "W", Value: int64(10), NoIndex: false}, + Property{Name: "X", Value: "ten", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "W", Value: int64(20), NoIndex: false}, + Property{Name: "X", Value: "twenty", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "W", Value: int64(30), NoIndex: false}, + Property{Name: "X", Value: "thirty", NoIndex: false}, + }, + }, + }, NoIndex: false}, + Property{Name: "J", Value: &Entity{ + Properties: []Property{ + Property{Name: "Y", Value: float64(3.14), NoIndex: false}, + }, + }, NoIndex: false}, + Property{Name: "Z", Value: true, NoIndex: false}, + }, + "", + "", + }, + { + "save props load outer-equivalent", + &PropertyList{ + Property{Name: "A", Value: int64(1), NoIndex: false}, + Property{Name: "I.W", Value: []interface{}{int64(10), int64(20), int64(30)}, NoIndex: false}, + Property{Name: "I.X", Value: []interface{}{"ten", "twenty", "thirty"}, NoIndex: false}, + Property{Name: "J.Y", Value: float64(3.14), NoIndex: false}, + Property{Name: "Z", Value: true, NoIndex: false}, + }, + &OuterEquivalent{ + A: 1, + IDotW: []int32{10, 20, 30}, + IDotX: []string{"ten", "twenty", "thirty"}, + JDotY: 3.14, + Z: true, + }, + "", + "", + }, + { + "dotted names save", + &Dotted{A: DottedA{B: DottedB{C: 88}}}, + &PropertyList{ + Property{Name: "A0.A1.A2", Value: &Entity{ + Properties: []Property{ + Property{Name: "B3", Value: &Entity{ + Properties: []Property{ + Property{Name: "C4.C5", Value: int64(88), NoIndex: false}, + }, + }, NoIndex: false}, + }, + }, NoIndex: false}, + }, + "", + "", + }, + { + "dotted names load", + &PropertyList{ + Property{Name: "A0.A1.A2", Value: &Entity{ + Properties: []Property{ + Property{Name: "B3", Value: &Entity{ + Properties: []Property{ + Property{Name: "C4.C5", Value: 99, NoIndex: false}, + }, + }, NoIndex: false}, + }, + }, NoIndex: false}, + }, + &Dotted{A: DottedA{B: DottedB{C: 99}}}, + "", + "", + }, + { + "save struct load deriver", + &X0{S: "s", I: 1}, + &Deriver{S: "s", Derived: "derived+s"}, + "", + "", + }, + { + "save deriver load struct", + &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"}, + &X0{S: "s"}, + "", + "", + }, + { + "zero time.Time", + &T{T: time.Time{}}, + &T{T: time.Time{}}, + "", + "", + }, + { + "time.Time near Unix zero time", + &T{T: time.Unix(0, 4e3)}, + &T{T: time.Unix(0, 4e3)}, + "", + "", + }, + { + "time.Time, far in the future", + &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, + &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)}, + "", + "", + }, + { + "time.Time, very far in the past", + &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)}, + &T{}, + "time value out of range", + "", + }, + { + "time.Time, very far in the future", + &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)}, + &T{}, + "time value out of range", + "", + }, + { + "structs", + &N0{ + X0: X0{S: "one", I: 2, i: 3}, + Nonymous: X0{S: "four", I: 5, i: 6}, + Ignore: "ignore", + Other: "other", + }, + &N0{ + X0: X0{S: "one", I: 2}, + Nonymous: X0{S: "four", I: 5}, + Other: "other", + }, + "", + "", + }, + { + "slice of structs", + &N1{ + X0: X0{S: "one", I: 2, i: 3}, + Nonymous: []X0{ + {S: "four", I: 5, i: 6}, + {S: "seven", I: 8, i: 9}, + {S: "ten", I: 11, i: 12}, + {S: "thirteen", I: 14, i: 15}, + }, + Ignore: "ignore", + Other: "other", + }, + &N1{ + X0: X0{S: "one", I: 2}, + Nonymous: []X0{ + {S: "four", I: 5}, + {S: "seven", I: 8}, + {S: "ten", I: 11}, + {S: "thirteen", I: 14}, + }, + Other: "other", + }, + "", + "", + }, + { + "structs with slices of structs", + &N2{ + N1: N1{ + X0: X0{S: "rouge"}, + Nonymous: []X0{ + {S: "rosso0"}, + {S: "rosso1"}, + }, + }, + Green: N1{ + X0: X0{S: "vert"}, + Nonymous: []X0{ + {S: "verde0"}, + {S: "verde1"}, + {S: "verde2"}, + }, + }, + Blue: N1{ + X0: X0{S: "bleu"}, + Nonymous: []X0{ + {S: "blu0"}, + {S: "blu1"}, + {S: "blu2"}, + {S: "blu3"}, + }, + }, + }, + &N2{ + N1: N1{ + X0: X0{S: "rouge"}, + Nonymous: []X0{ + {S: "rosso0"}, + {S: "rosso1"}, + }, + }, + Green: N1{ + X0: X0{S: "vert"}, + Nonymous: []X0{ + {S: "verde0"}, + {S: "verde1"}, + {S: "verde2"}, + }, + }, + Blue: N1{ + X0: X0{S: "bleu"}, + Nonymous: []X0{ + {S: "blu0"}, + {S: "blu1"}, + {S: "blu2"}, + {S: "blu3"}, + }, + }, + }, + "", + "", + }, + { + "save structs load props", + &N2{ + N1: N1{ + X0: X0{S: "rouge"}, + Nonymous: []X0{ + {S: "rosso0"}, + {S: "rosso1"}, + }, + }, + Green: N1{ + X0: X0{S: "vert"}, + Nonymous: []X0{ + {S: "verde0"}, + {S: "verde1"}, + {S: "verde2"}, + }, + }, + Blue: N1{ + X0: X0{S: "bleu"}, + Nonymous: []X0{ + {S: "blu0"}, + {S: "blu1"}, + {S: "blu2"}, + {S: "blu3"}, + }, + }, + }, + &PropertyList{ + Property{Name: "Blue", Value: &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "Nonymous", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "blu0", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "blu1", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "blu2", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "blu3", NoIndex: false}, + }, + }, + }, NoIndex: false}, + Property{Name: "Other", Value: "", NoIndex: false}, + Property{Name: "S", Value: "bleu", NoIndex: false}, + }, + }, NoIndex: false}, + Property{Name: "green", Value: &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "Nonymous", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "verde0", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "verde1", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "verde2", NoIndex: false}, + }, + }, + }, NoIndex: false}, + Property{Name: "Other", Value: "", NoIndex: false}, + Property{Name: "S", Value: "vert", NoIndex: false}, + }, + }, NoIndex: false}, + Property{Name: "red", Value: &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "Nonymous", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "rosso0", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "I", Value: int64(0), NoIndex: false}, + Property{Name: "S", Value: "rosso1", NoIndex: false}, + }, + }, + }, NoIndex: false}, + Property{Name: "Other", Value: "", NoIndex: false}, + Property{Name: "S", Value: "rouge", NoIndex: false}, + }, + }, NoIndex: false}, + }, + "", + "", + }, + { + "nested entity with key", + &WithNestedEntityWithKey{ + N: EntityWithKey{ + I: 12, + S: "abcd", + K: testKey0, + }, + }, + &WithNestedEntityWithKey{ + N: EntityWithKey{ + I: 12, + S: "abcd", + K: testKey0, + }, + }, + "", + "", + }, + { + "entity with key at top level", + &EntityWithKey{ + I: 12, + S: "abc", + K: testKey0, + }, + &EntityWithKey{ + I: 12, + S: "abc", + K: testKey0, + }, + "", + "", + }, + { + "entity with key at top level (key is populated on load)", + &EntityWithKey{ + I: 12, + S: "abc", + }, + &EntityWithKey{ + I: 12, + S: "abc", + K: testKey0, + }, + "", + "", + }, + { + "__key__ field not a *Key", + &NestedWithNonKeyField{ + N: WithNonKeyField{ + I: 12, + K: "abcd", + }, + }, + &NestedWithNonKeyField{ + N: WithNonKeyField{ + I: 12, + K: "abcd", + }, + }, + "datastore: __key__ field on struct datastore.WithNonKeyField is not a *datastore.Key", + "", + }, + { + "save struct with ptr to struct fields", + &PtrToStructField{ + &Basic{ + A: "b", + }, + &Basic{ + A: "c", + }, + &Basic{ + A: "anon", + }, + []*Basic{ + &Basic{ + A: "slice0", + }, + &Basic{ + A: "slice1", + }, + }, + }, + &PropertyList{ + Property{Name: "A", Value: "anon", NoIndex: false}, + Property{Name: "B", Value: &Entity{ + Properties: []Property{ + Property{Name: "A", Value: "b", NoIndex: false}, + }, + }}, + Property{Name: "D", Value: []interface{}{ + &Entity{ + Properties: []Property{ + Property{Name: "A", Value: "slice0", NoIndex: false}, + }, + }, + &Entity{ + Properties: []Property{ + Property{Name: "A", Value: "slice1", NoIndex: false}, + }, + }, + }, NoIndex: false}, + Property{Name: "c", Value: &Entity{ + Properties: []Property{ + Property{Name: "A", Value: "c", NoIndex: true}, + }, + }, NoIndex: true}, + }, + "", + "", + }, + { + "save and load struct with ptr to struct fields", + &PtrToStructField{ + &Basic{ + A: "b", + }, + &Basic{ + A: "c", + }, + &Basic{ + A: "anon", + }, + []*Basic{ + &Basic{ + A: "slice0", + }, + &Basic{ + A: "slice1", + }, + }, + }, + &PtrToStructField{ + &Basic{ + A: "b", + }, + &Basic{ + A: "c", + }, + &Basic{ + A: "anon", + }, + []*Basic{ + &Basic{ + A: "slice0", + }, + &Basic{ + A: "slice1", + }, + }, + }, + "", + "", + }, + { + "save struct with pointer to int field", + &PtrToInt{ + I: &two, + }, + &PtrToInt{}, + "unsupported struct field", + "", + }, + { + "struct with nil ptr to struct fields", + &PtrToStructField{ + nil, + nil, + nil, + nil, + }, + new(PropertyList), + "", + "", + }, + { + "nested load entity with key", + &WithNestedEntityWithKey{ + N: EntityWithKey{ + I: 12, + S: "abcd", + K: testKey0, + }, + }, + &PropertyList{ + Property{Name: "N", Value: &Entity{ + Key: testKey0, + Properties: []Property{ + Property{Name: "I", Value: int64(12), NoIndex: false}, + Property{Name: "S", Value: "abcd", NoIndex: false}, + }, + }, + NoIndex: false}, + }, + "", + "", + }, + { + "nested save entity with key", + &PropertyList{ + Property{Name: "N", Value: &Entity{ + Key: testKey0, + Properties: []Property{ + Property{Name: "I", Value: int64(12), NoIndex: false}, + Property{Name: "S", Value: "abcd", NoIndex: false}, + }, + }, NoIndex: false}, + }, + + &WithNestedEntityWithKey{ + N: EntityWithKey{ + I: 12, + S: "abcd", + K: testKey0, + }, + }, + "", + "", + }, + { + "anonymous field with tag", + &N3{ + C3: C3{C: "s"}, + }, + &PropertyList{ + Property{Name: "red", Value: &Entity{ + Properties: []Property{ + Property{Name: "C", Value: "s", NoIndex: false}, + }, + }, NoIndex: false}, + }, + "", + "", + }, + { + "unexported anonymous field", + &N4{ + c4: c4{C: "s"}, + }, + &PropertyList{ + Property{Name: "C", Value: "s", NoIndex: false}, + }, + "", + "", + }, + { + "unexported anonymous field with tag", + &N5{ + c4: c4{C: "s"}, + }, + new(PropertyList), + "", + "", + }, + { + "save props load structs with ragged fields", + &PropertyList{ + Property{Name: "red.S", Value: "rot", NoIndex: false}, + Property{Name: "green.Nonymous.I", Value: []interface{}{int64(10), int64(11), int64(12), int64(13)}, NoIndex: false}, + Property{Name: "Blue.Nonymous.I", Value: []interface{}{int64(20), int64(21)}, NoIndex: false}, + Property{Name: "Blue.Nonymous.S", Value: []interface{}{"blau0", "blau1", "blau2"}, NoIndex: false}, + }, + &N2{ + N1: N1{ + X0: X0{S: "rot"}, + }, + Green: N1{ + Nonymous: []X0{ + {I: 10}, + {I: 11}, + {I: 12}, + {I: 13}, + }, + }, + Blue: N1{ + Nonymous: []X0{ + {S: "blau0", I: 20}, + {S: "blau1", I: 21}, + {S: "blau2"}, + }, + }, + }, + "", + "", + }, + { + "save structs with noindex tags", + &struct { + A struct { + X string `datastore:",noindex"` + Y string + } `datastore:",noindex"` + B struct { + X string `datastore:",noindex"` + Y string + } + }{}, + &PropertyList{ + Property{Name: "A", Value: &Entity{ + Properties: []Property{ + Property{Name: "X", Value: "", NoIndex: true}, + Property{Name: "Y", Value: "", NoIndex: true}, + }, + }, NoIndex: true}, + Property{Name: "B", Value: &Entity{ + Properties: []Property{ + Property{Name: "X", Value: "", NoIndex: true}, + Property{Name: "Y", Value: "", NoIndex: false}, + }, + }, NoIndex: false}, + }, + "", + "", + }, + { + "embedded struct with name override", + &struct { + Inner1 `datastore:"foo"` + }{}, + &PropertyList{ + Property{Name: "foo", Value: &Entity{ + Properties: []Property{ + Property{Name: "W", Value: int64(0), NoIndex: false}, + Property{Name: "X", Value: "", NoIndex: false}, + }, + }, NoIndex: false}, + }, + "", + "", + }, + { + "slice of slices", + &SliceOfSlices{}, + nil, + "flattening nested structs leads to a slice of slices", + "", + }, + { + "recursive struct", + &Recursive{}, + &Recursive{}, + "", + "", + }, + { + "mutually recursive struct", + &MutuallyRecursive0{}, + &MutuallyRecursive0{}, + "", + "", + }, + { + "non-exported struct fields", + &struct { + i, J int64 + }{i: 1, J: 2}, + &PropertyList{ + Property{Name: "J", Value: int64(2), NoIndex: false}, + }, + "", + "", + }, + { + "json.RawMessage", + &struct { + J json.RawMessage + }{ + J: json.RawMessage("rawr"), + }, + &PropertyList{ + Property{Name: "J", Value: []byte("rawr"), NoIndex: false}, + }, + "", + "", + }, + { + "json.RawMessage to myBlob", + &struct { + B json.RawMessage + }{ + B: json.RawMessage("rawr"), + }, + &B2{B: myBlob("rawr")}, + "", + "", + }, + { + "repeated property names", + &PropertyList{ + Property{Name: "A", Value: ""}, + Property{Name: "A", Value: ""}, + }, + nil, + "duplicate Property", + "", + }, + { + "embedded time field", + &SpecialTime{MyTime: EmbeddedTime{ts}}, + &SpecialTime{MyTime: EmbeddedTime{ts}}, + "", + "", + }, + { + "embedded time load", + &PropertyList{ + Property{Name: "MyTime.Time", Value: ts}, + }, + &SpecialTime{MyTime: EmbeddedTime{ts}}, + "", + "", + }, +} + +// checkErr returns the empty string if either both want and err are zero, +// or if want is a non-empty substring of err's string representation. +func checkErr(want string, err error) string { + if err != nil { + got := err.Error() + if want == "" || strings.Index(got, want) == -1 { + return got + } + } else if want != "" { + return fmt.Sprintf("want error %q", want) + } + return "" +} + +func TestRoundTrip(t *testing.T) { + for _, tc := range testCases { + p, err := saveEntity(testKey0, tc.src) + if s := checkErr(tc.putErr, err); s != "" { + t.Errorf("%s: save: %s", tc.desc, s) + continue + } + if p == nil { + continue + } + var got interface{} + if _, ok := tc.want.(*PropertyList); ok { + got = new(PropertyList) + } else { + got = reflect.New(reflect.TypeOf(tc.want).Elem()).Interface() + } + err = loadEntityProto(got, p) + if s := checkErr(tc.getErr, err); s != "" { + t.Errorf("%s: load: %s", tc.desc, s) + continue + } + if pl, ok := got.(*PropertyList); ok { + // Sort by name to make sure we have a deterministic order. + sortPL(*pl) + } + + equal := false + switch v := got.(type) { + // Round tripping a time.Time can result in a different time.Location: Local instead of UTC. + // We therefore test equality explicitly, instead of relying on reflect.DeepEqual. + case *T: + equal = v.T.Equal(tc.want.(*T).T) + case *SpecialTime: + equal = v.MyTime.Equal(tc.want.(*SpecialTime).MyTime.Time) + default: + equal = reflect.DeepEqual(got, tc.want) + } + if !equal { + t.Errorf("%s: compare:\ngot: %+#v\nwant: %+#v", tc.desc, got, tc.want) + continue + } + } +} + +type aPtrPLS struct { + Count int +} + +func (pls *aPtrPLS) Load([]Property) error { + pls.Count += 1 + return nil +} + +func (pls *aPtrPLS) Save() ([]Property, error) { + return []Property{{Name: "Count", Value: 4}}, nil +} + +type aValuePLS struct { + Count int +} + +func (pls aValuePLS) Load([]Property) error { + pls.Count += 2 + return nil +} + +func (pls aValuePLS) Save() ([]Property, error) { + return []Property{{Name: "Count", Value: 8}}, nil +} + +type aValuePtrPLS struct { + Count int +} + +func (pls *aValuePtrPLS) Load([]Property) error { + pls.Count = 11 + return nil +} + +func (pls *aValuePtrPLS) Save() ([]Property, error) { + return []Property{{Name: "Count", Value: 12}}, nil +} + +type aNotPLS struct { + Count int +} + +type plsString string + +func (s *plsString) Load([]Property) error { + *s = "LOADED" + return nil +} + +func (s *plsString) Save() ([]Property, error) { + return []Property{{Name: "SS", Value: "SAVED"}}, nil +} + +func ptrToplsString(s string) *plsString { + plsStr := plsString(s) + return &plsStr +} + +type aSubPLS struct { + Foo string + Bar *aPtrPLS + Baz aValuePtrPLS + S plsString +} + +type aSubNotPLS struct { + Foo string + Bar *aNotPLS +} + +type aSubPLSErr struct { + Foo string + Bar aValuePLS +} + +type aSubPLSNoErr struct { + Foo string + Bar aPtrPLS +} + +type GrandparentFlatten struct { + Parent Parent `datastore:",flatten"` +} + +type GrandparentOfPtrFlatten struct { + Parent ParentOfPtr `datastore:",flatten"` +} + +type GrandparentOfSlice struct { + Parent ParentOfSlice +} + +type GrandparentOfSlicePtrs struct { + Parent ParentOfSlicePtrs +} + +type GrandparentOfSliceFlatten struct { + Parent ParentOfSlice `datastore:",flatten"` +} + +type GrandparentOfSlicePtrsFlatten struct { + Parent ParentOfSlicePtrs `datastore:",flatten"` +} + +type Grandparent struct { + Parent Parent +} + +type Parent struct { + Child Child + String plsString +} + +type ParentOfPtr struct { + Child *Child + String *plsString +} + +type ParentOfSlice struct { + Children []Child + Strings []plsString +} + +type ParentOfSlicePtrs struct { + Children []*Child + Strings []*plsString +} + +type Child struct { + I int + Grandchild Grandchild +} + +type Grandchild struct { + S string +} + +func (c *Child) Load(props []Property) error { + for _, p := range props { + if p.Name == "I" { + c.I += 1 + } else if p.Name == "Grandchild.S" { + c.Grandchild.S = "grandchild loaded" + } + } + + return nil +} + +func (c *Child) Save() ([]Property, error) { + v := c.I + 1 + return []Property{ + {Name: "I", Value: v}, + {Name: "Grandchild.S", Value: fmt.Sprintf("grandchild saved %d", v)}, + }, nil +} + +func TestLoadSavePLS(t *testing.T) { + type testCase struct { + desc string + src interface{} + wantSave *pb.Entity + wantLoad interface{} + saveErr string + loadErr string + } + + testCases := []testCase{ + { + desc: "non-struct implements PLS (top-level)", + src: ptrToplsString("hello"), + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + wantLoad: ptrToplsString("LOADED"), + }, + { + desc: "substructs do implement PLS", + src: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 2}, Baz: aValuePtrPLS{Count: 15}, S: "something"}, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, + "Bar": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}}, + }, + }, + }}, + "Baz": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}}, + }, + }, + }}, + "S": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }, + }, + wantLoad: &aSubPLS{Foo: "foo", Bar: &aPtrPLS{Count: 1}, Baz: aValuePtrPLS{Count: 11}, S: "LOADED"}, + }, + { + desc: "substruct (ptr) does implement PLS, nil valued substruct", + src: &aSubPLS{Foo: "foo", S: "something"}, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, + "Baz": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}}, + }, + }, + }}, + "S": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }, + }, + wantLoad: &aSubPLS{Foo: "foo", Baz: aValuePtrPLS{Count: 11}, S: "LOADED"}, + }, + { + desc: "substruct (ptr) does not implement PLS", + src: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}}, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, + "Bar": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + wantLoad: &aSubNotPLS{Foo: "foo", Bar: &aNotPLS{Count: 2}}, + }, + { + desc: "substruct (value) does implement PLS, error on save", + src: &aSubPLSErr{Foo: "foo", Bar: aValuePLS{Count: 2}}, + wantSave: (*pb.Entity)(nil), + wantLoad: &aSubPLSErr{}, + saveErr: "PropertyLoadSaver methods must be implemented on a pointer", + }, + { + desc: "substruct (value) does implement PLS, error on load", + src: &aSubPLSNoErr{Foo: "foo", Bar: aPtrPLS{Count: 2}}, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Foo": {ValueType: &pb.Value_StringValue{StringValue: "foo"}}, + "Bar": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Count": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}}, + }, + }, + }}, + }, + }, + wantLoad: &aSubPLSErr{}, + loadErr: "PropertyLoadSaver methods must be implemented on a pointer", + }, + + { + desc: "parent does not have flatten option, child impl PLS", + src: &Grandparent{ + Parent: Parent{ + Child: Child{ + I: 9, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + String: plsString("something"), + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Child": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, + }, + }, + }}, + "String": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }, + }, + }}, + }, + }, + wantLoad: &Grandparent{ + Parent: Parent{ + Child: Child{ + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + String: "LOADED", + }, + }, + }, + { + desc: "parent has flatten option enabled, child impl PLS", + src: &GrandparentFlatten{ + Parent: Parent{ + Child: Child{ + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + String: plsString("something"), + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + "Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + "Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + wantLoad: &GrandparentFlatten{ + Parent: Parent{ + Child: Child{ + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + String: "LOADED", + }, + }, + }, + + { + desc: "parent has flatten option enabled, child (ptr to) impl PLS", + src: &GrandparentOfPtrFlatten{ + Parent: ParentOfPtr{ + Child: &Child{ + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + String: ptrToplsString("something"), + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent.Child.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + "Parent.Child.Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + "Parent.String.SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + wantLoad: &GrandparentOfPtrFlatten{ + Parent: ParentOfPtr{ + Child: &Child{ + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + String: ptrToplsString("LOADED"), + }, + }, + }, + { + desc: "children (slice of) impl PLS", + src: &GrandparentOfSlice{ + Parent: ParentOfSlice{ + Children: []Child{ + { + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + { + I: 9, + Grandchild: Grandchild{ + S: "BAD2", + }, + }, + }, + Strings: []plsString{ + "something1", + "something2", + }, + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Children": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + }, + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, + }, + }, + }}, + }}, + }}, + "Strings": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }}, + }}, + }, + }, + }}, + }, + }, + wantLoad: &GrandparentOfSlice{ + Parent: ParentOfSlice{ + Children: []Child{ + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + }, + Strings: []plsString{ + "LOADED", + "LOADED", + }, + }, + }, + }, + { + desc: "children (slice of ptrs) impl PLS", + src: &GrandparentOfSlicePtrs{ + Parent: ParentOfSlicePtrs{ + Children: []*Child{ + { + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + { + I: 9, + Grandchild: Grandchild{ + S: "BAD2", + }, + }, + }, + Strings: []*plsString{ + ptrToplsString("something1"), + ptrToplsString("something2"), + }, + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "Children": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + }, + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + "Grandchild.S": {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, + }, + }, + }}, + }}, + }}, + "Strings": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }}, + }}, + }, + }, + }}, + }, + }, + wantLoad: &GrandparentOfSlicePtrs{ + Parent: ParentOfSlicePtrs{ + Children: []*Child{ + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + }, + Strings: []*plsString{ + ptrToplsString("LOADED"), + ptrToplsString("LOADED"), + }, + }, + }, + }, + { + desc: "parent has flatten option, children (slice of) impl PLS", + src: &GrandparentOfSliceFlatten{ + Parent: ParentOfSlice{ + Children: []Child{ + { + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + { + I: 9, + Grandchild: Grandchild{ + S: "BAD2", + }, + }, + }, + Strings: []plsString{ + "something1", + "something2", + }, + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + }, + }, + }}, + "Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, + }, + }, + }}, + "Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }, + }, + wantLoad: &GrandparentOfSliceFlatten{ + Parent: ParentOfSlice{ + Children: []Child{ + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + }, + Strings: []plsString{ + "LOADED", + "LOADED", + }, + }, + }, + }, + { + desc: "parent has flatten option, children (slice of ptrs) impl PLS", + src: &GrandparentOfSlicePtrsFlatten{ + Parent: ParentOfSlicePtrs{ + Children: []*Child{ + { + I: 7, + Grandchild: Grandchild{ + S: "BAD", + }, + }, + { + I: 9, + Grandchild: Grandchild{ + S: "BAD2", + }, + }, + }, + Strings: []*plsString{ + ptrToplsString("something1"), + ptrToplsString("something1"), + }, + }, + }, + wantSave: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Parent.Children.I": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_IntegerValue{IntegerValue: 8}}, + {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + }, + }, + }}, + "Parent.Children.Grandchild.S": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 8"}}, + {ValueType: &pb.Value_StringValue{StringValue: "grandchild saved 10"}}, + }, + }, + }}, + "Parent.Strings.SS": {ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + {ValueType: &pb.Value_StringValue{StringValue: "SAVED"}}, + }, + }, + }}, + }, + }, + wantLoad: &GrandparentOfSlicePtrsFlatten{ + Parent: ParentOfSlicePtrs{ + Children: []*Child{ + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + { + I: 1, + Grandchild: Grandchild{ + S: "grandchild loaded", + }, + }, + }, + Strings: []*plsString{ + ptrToplsString("LOADED"), + ptrToplsString("LOADED"), + }, + }, + }, + }, + } + + for _, tc := range testCases { + e, err := saveEntity(testKey0, tc.src) + if tc.saveErr == "" { // Want no error. + if err != nil { + t.Errorf("%s: save: %v", tc.desc, err) + continue + } + if !reflect.DeepEqual(e, tc.wantSave) { + t.Errorf("%s: save: \ngot: %+v\nwant: %+v", tc.desc, e, tc.wantSave) + continue + } + } else { // Want error. + if err == nil { + t.Errorf("%s: save: want err", tc.desc) + continue + } + if !strings.Contains(err.Error(), tc.saveErr) { + t.Errorf("%s: save: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.saveErr) + } + continue + } + + gota := reflect.New(reflect.TypeOf(tc.wantLoad).Elem()).Interface() + err = loadEntityProto(gota, e) + if tc.loadErr == "" { // Want no error. + if err != nil { + t.Errorf("%s: load: %v", tc.desc, err) + continue + } + if !reflect.DeepEqual(gota, tc.wantLoad) { + t.Errorf("%s: load: \ngot: %+v\nwant: %+v", tc.desc, gota, tc.wantLoad) + continue + } + } else { // Want error. + if err == nil { + t.Errorf("%s: load: want err", tc.desc) + continue + } + if !strings.Contains(err.Error(), tc.loadErr) { + t.Errorf("%s: load: \ngot err '%s'\nwant err '%s'", tc.desc, err.Error(), tc.loadErr) + } + } + } +} + +func TestQueryConstruction(t *testing.T) { + tests := []struct { + q, exp *Query + err string + }{ + { + q: NewQuery("Foo"), + exp: &Query{ + kind: "Foo", + limit: -1, + }, + }, + { + // Regular filtered query with standard spacing. + q: NewQuery("Foo").Filter("foo >", 7), + exp: &Query{ + kind: "Foo", + filter: []filter{ + { + FieldName: "foo", + Op: greaterThan, + Value: 7, + }, + }, + limit: -1, + }, + }, + { + // Filtered query with no spacing. + q: NewQuery("Foo").Filter("foo=", 6), + exp: &Query{ + kind: "Foo", + filter: []filter{ + { + FieldName: "foo", + Op: equal, + Value: 6, + }, + }, + limit: -1, + }, + }, + { + // Filtered query with funky spacing. + q: NewQuery("Foo").Filter(" foo< ", 8), + exp: &Query{ + kind: "Foo", + filter: []filter{ + { + FieldName: "foo", + Op: lessThan, + Value: 8, + }, + }, + limit: -1, + }, + }, + { + // Filtered query with multicharacter op. + q: NewQuery("Foo").Filter("foo >=", 9), + exp: &Query{ + kind: "Foo", + filter: []filter{ + { + FieldName: "foo", + Op: greaterEq, + Value: 9, + }, + }, + limit: -1, + }, + }, + { + // Query with ordering. + q: NewQuery("Foo").Order("bar"), + exp: &Query{ + kind: "Foo", + order: []order{ + { + FieldName: "bar", + Direction: ascending, + }, + }, + limit: -1, + }, + }, + { + // Query with reverse ordering, and funky spacing. + q: NewQuery("Foo").Order(" - bar"), + exp: &Query{ + kind: "Foo", + order: []order{ + { + FieldName: "bar", + Direction: descending, + }, + }, + limit: -1, + }, + }, + { + // Query with an empty ordering. + q: NewQuery("Foo").Order(""), + err: "empty order", + }, + { + // Query with a + ordering. + q: NewQuery("Foo").Order("+bar"), + err: "invalid order", + }, + } + for i, test := range tests { + if test.q.err != nil { + got := test.q.err.Error() + if !strings.Contains(got, test.err) { + t.Errorf("%d: error mismatch: got %q want something containing %q", i, got, test.err) + } + continue + } + if !reflect.DeepEqual(test.q, test.exp) { + t.Errorf("%d: mismatch: got %v want %v", i, test.q, test.exp) + } + } +} + +func TestPutMultiTypes(t *testing.T) { + ctx := context.Background() + type S struct { + A int + B string + } + + testCases := []struct { + desc string + src interface{} + wantErr bool + }{ + // Test cases to check each of the valid input types for src. + // Each case has the same elements. + { + desc: "type []struct", + src: []S{ + {1, "one"}, {2, "two"}, + }, + }, + { + desc: "type []*struct", + src: []*S{ + {1, "one"}, {2, "two"}, + }, + }, + { + desc: "type []interface{} with PLS elems", + src: []interface{}{ + &PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, + &PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, + }, + }, + { + desc: "type []interface{} with struct ptr elems", + src: []interface{}{ + &S{1, "one"}, &S{2, "two"}, + }, + }, + { + desc: "type []PropertyLoadSaver{}", + src: []PropertyLoadSaver{ + &PropertyList{Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, + &PropertyList{Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, + }, + }, + { + desc: "type []P (non-pointer, *P implements PropertyLoadSaver)", + src: []PropertyList{ + {Property{Name: "A", Value: 1}, Property{Name: "B", Value: "one"}}, + {Property{Name: "A", Value: 2}, Property{Name: "B", Value: "two"}}, + }, + }, + // Test some invalid cases. + { + desc: "type []interface{} with struct elems", + src: []interface{}{ + S{1, "one"}, S{2, "two"}, + }, + wantErr: true, + }, + { + desc: "PropertyList", + src: PropertyList{ + Property{Name: "A", Value: 1}, + Property{Name: "B", Value: "one"}, + }, + wantErr: true, + }, + { + desc: "type []int", + src: []int{1, 2}, + wantErr: true, + }, + { + desc: "not a slice", + src: S{1, "one"}, + wantErr: true, + }, + } + + // Use the same keys and expected entities for all tests. + keys := []*Key{ + NameKey("testKind", "first", nil), + NameKey("testKind", "second", nil), + } + want := []*pb.Mutation{ + {Operation: &pb.Mutation_Upsert{ + Upsert: &pb.Entity{ + Key: keyToProto(keys[0]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, + }, + }}}, + {Operation: &pb.Mutation_Upsert{ + Upsert: &pb.Entity{ + Key: keyToProto(keys[1]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + }, + }}}, + } + + for _, tt := range testCases { + // Set up a fake client which captures upserts. + var got []*pb.Mutation + client := &Client{ + client: &fakeClient{ + commitFn: func(req *pb.CommitRequest) (*pb.CommitResponse, error) { + got = req.Mutations + return &pb.CommitResponse{}, nil + }, + }, + } + + _, err := client.PutMulti(ctx, keys, tt.src) + if err != nil { + if !tt.wantErr { + t.Errorf("%s: error %v", tt.desc, err) + } + continue + } + if tt.wantErr { + t.Errorf("%s: wanted error, but none returned", tt.desc) + continue + } + if len(got) != len(want) { + t.Errorf("%s: got %d entities, want %d", tt.desc, len(got), len(want)) + continue + } + for i, e := range got { + if !proto.Equal(e, want[i]) { + t.Logf("%s: entity %d doesn't match\ngot: %v\nwant: %v", tt.desc, i, e, want[i]) + } + } + } +} + +func TestNoIndexOnSliceProperties(t *testing.T) { + // Check that ExcludeFromIndexes is set on the inner elements, + // rather than the top-level ArrayValue value. + pl := PropertyList{ + Property{ + Name: "repeated", + Value: []interface{}{ + 123, + false, + "short", + strings.Repeat("a", 1503), + }, + NoIndex: true, + }, + } + key := NameKey("dummy", "dummy", nil) + + entity, err := saveEntity(key, &pl) + if err != nil { + t.Fatalf("saveEntity: %v", err) + } + + want := &pb.Value{ + ValueType: &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: []*pb.Value{ + {ValueType: &pb.Value_IntegerValue{IntegerValue: 123}, ExcludeFromIndexes: true}, + {ValueType: &pb.Value_BooleanValue{BooleanValue: false}, ExcludeFromIndexes: true}, + {ValueType: &pb.Value_StringValue{StringValue: "short"}, ExcludeFromIndexes: true}, + {ValueType: &pb.Value_StringValue{StringValue: strings.Repeat("a", 1503)}, ExcludeFromIndexes: true}, + }}}, + } + if got := entity.Properties["repeated"]; !proto.Equal(got, want) { + t.Errorf("Entity proto differs\ngot: %v\nwant: %v", got, want) + } +} + +type byName PropertyList + +func (s byName) Len() int { return len(s) } +func (s byName) Less(i, j int) bool { return s[i].Name < s[j].Name } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// sortPL sorts the property list by property name, and +// recursively sorts any nested property lists, or nested slices of +// property lists. +func sortPL(pl PropertyList) { + sort.Stable(byName(pl)) + for _, p := range pl { + switch p.Value.(type) { + case *Entity: + sortPL(p.Value.(*Entity).Properties) + case []interface{}: + for _, p2 := range p.Value.([]interface{}) { + if nent, ok := p2.(*Entity); ok { + sortPL(nent.Properties) + } + } + } + } +} + +func TestValidGeoPoint(t *testing.T) { + testCases := []struct { + desc string + pt GeoPoint + want bool + }{ + { + "valid", + GeoPoint{67.21, 13.37}, + true, + }, + { + "high lat", + GeoPoint{-90.01, 13.37}, + false, + }, + { + "low lat", + GeoPoint{90.01, 13.37}, + false, + }, + { + "high lng", + GeoPoint{67.21, 182}, + false, + }, + { + "low lng", + GeoPoint{67.21, -181}, + false, + }, + } + + for _, tc := range testCases { + if got := tc.pt.Valid(); got != tc.want { + t.Errorf("%s: got %v, want %v", tc.desc, got, tc.want) + } + } +} + +func TestPutInvalidEntity(t *testing.T) { + // Test that trying to put an invalid entity always returns the correct error + // type. + + // Fake client that can pretend to start a transaction. + fakeClient := &fakeDatastoreClient{ + beginTransaction: func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) { + return &pb.BeginTransactionResponse{ + Transaction: []byte("deadbeef"), + }, nil + }, + } + client := &Client{ + client: fakeClient, + } + + ctx := context.Background() + key := IncompleteKey("kind", nil) + + _, err := client.Put(ctx, key, "invalid entity") + if err != ErrInvalidEntityType { + t.Errorf("client.Put returned err %v, want %v", err, ErrInvalidEntityType) + } + + _, err = client.PutMulti(ctx, []*Key{key}, []interface{}{"invalid entity"}) + if me, ok := err.(MultiError); !ok { + t.Errorf("client.PutMulti returned err %v, want MultiError type", err) + } else if len(me) != 1 || me[0] != ErrInvalidEntityType { + t.Errorf("client.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err) + } + + client.RunInTransaction(ctx, func(tx *Transaction) error { + _, err := tx.Put(key, "invalid entity") + if err != ErrInvalidEntityType { + t.Errorf("tx.Put returned err %v, want %v", err, ErrInvalidEntityType) + } + + _, err = tx.PutMulti([]*Key{key}, []interface{}{"invalid entity"}) + if me, ok := err.(MultiError); !ok { + t.Errorf("tx.PutMulti returned err %v, want MultiError type", err) + } else if len(me) != 1 || me[0] != ErrInvalidEntityType { + t.Errorf("tx.PutMulti returned err %v, want MulitError{ErrInvalidEntityType}", err) + } + + return errors.New("bang!") // Return error: we don't actually want to commit. + }) +} + +func TestDeferred(t *testing.T) { + type Ent struct { + A int + B string + } + + keys := []*Key{ + NameKey("testKind", "first", nil), + NameKey("testKind", "second", nil), + } + + entity1 := &pb.Entity{ + Key: keyToProto(keys[0]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, + }, + } + entity2 := &pb.Entity{ + Key: keyToProto(keys[1]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + }, + } + + // count keeps track of the number of times fakeClient.lookup has been + // called. + var count int + // Fake client that will return Deferred keys in resp on the first call. + fakeClient := &fakeDatastoreClient{ + lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { + count++ + // On the first call, we return deferred keys. + if count == 1 { + return &pb.LookupResponse{ + Found: []*pb.EntityResult{ + { + Entity: entity1, + Version: 1, + }, + }, + Deferred: []*pb.Key{ + keyToProto(keys[1]), + }, + }, nil + } + + // On the second call, we do not return any more deferred keys. + return &pb.LookupResponse{ + Found: []*pb.EntityResult{ + { + Entity: entity2, + Version: 1, + }, + }, + }, nil + }, + } + client := &Client{ + client: fakeClient, + } + + ctx := context.Background() + + dst := make([]Ent, len(keys)) + err := client.GetMulti(ctx, keys, dst) + if err != nil { + t.Fatalf("client.Get: %v", err) + } + + if count != 2 { + t.Fatalf("expected client.lookup to be called 2 times. Got %d", count) + } + + if len(dst) != 2 { + t.Fatalf("expected 2 entities returned, got %d", len(dst)) + } + + for _, e := range dst { + if e.A == 1 { + if e.B != "one" { + t.Fatalf("unexpected entity %+v", e) + } + } else if e.A == 2 { + if e.B != "two" { + t.Fatalf("unexpected entity %+v", e) + } + } else { + t.Fatalf("unexpected entity %+v", e) + } + } + +} + +type KeyLoaderEnt struct { + A int + K *Key +} + +func (e *KeyLoaderEnt) Load(p []Property) error { + e.A = 2 + return nil +} + +func (e *KeyLoaderEnt) LoadKey(k *Key) error { + e.K = k + return nil +} + +func (e *KeyLoaderEnt) Save() ([]Property, error) { + return []Property{{Name: "A", Value: int64(3)}}, nil +} + +func TestKeyLoaderEndToEnd(t *testing.T) { + keys := []*Key{ + NameKey("testKind", "first", nil), + NameKey("testKind", "second", nil), + } + + entity1 := &pb.Entity{ + Key: keyToProto(keys[0]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "one"}}, + }, + } + entity2 := &pb.Entity{ + Key: keyToProto(keys[1]), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + "B": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + }, + } + + fakeClient := &fakeDatastoreClient{ + lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { + return &pb.LookupResponse{ + Found: []*pb.EntityResult{ + { + Entity: entity1, + Version: 1, + }, + { + Entity: entity2, + Version: 1, + }, + }, + }, nil + }, + } + client := &Client{ + client: fakeClient, + } + + ctx := context.Background() + + dst := make([]*KeyLoaderEnt, len(keys)) + err := client.GetMulti(ctx, keys, dst) + if err != nil { + t.Fatalf("client.Get: %v", err) + } + + for i := range dst { + if !reflect.DeepEqual(dst[i].K, keys[i]) { + t.Fatalf("unexpected entity %d to have key %+v, got %+v", i, keys[i], dst[i].K) + } + } +} + +func TestDeferredMissing(t *testing.T) { + type Ent struct { + A int + B string + } + + keys := []*Key{ + NameKey("testKind", "first", nil), + NameKey("testKind", "second", nil), + } + + entity1 := &pb.Entity{ + Key: keyToProto(keys[0]), + } + entity2 := &pb.Entity{ + Key: keyToProto(keys[1]), + } + + var count int + fakeClient := &fakeDatastoreClient{ + lookup: func(*pb.LookupRequest) (*pb.LookupResponse, error) { + count++ + + if count == 1 { + return &pb.LookupResponse{ + Missing: []*pb.EntityResult{ + { + Entity: entity1, + Version: 1, + }, + }, + Deferred: []*pb.Key{ + keyToProto(keys[1]), + }, + }, nil + } + + return &pb.LookupResponse{ + Missing: []*pb.EntityResult{ + { + Entity: entity2, + Version: 1, + }, + }, + }, nil + }, + } + client := &Client{ + client: fakeClient, + } + + ctx := context.Background() + + dst := make([]Ent, len(keys)) + err := client.GetMulti(ctx, keys, dst) + errs, ok := err.(MultiError) + if !ok { + t.Fatalf("expected error returns to be MultiError; got %v", err) + } + if len(errs) != 2 { + t.Fatalf("expected 2 errors returns, got %d", len(errs)) + } + if errs[0] != ErrNoSuchEntity { + t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[0]) + } + if errs[1] != ErrNoSuchEntity { + t.Fatalf("expected error to be ErrNoSuchEntity; got %v", errs[1]) + } + + if count != 2 { + t.Fatalf("expected client.lookup to be called 2 times. Got %d", count) + } + + if len(dst) != 2 { + t.Fatalf("expected 2 entities returned, got %d", len(dst)) + } + + for _, e := range dst { + if e.A != 0 || e.B != "" { + t.Fatalf("unexpected entity %+v", e) + } + } +} + +type fakeDatastoreClient struct { + pb.DatastoreClient + + // Optional handlers for the datastore methods. + // Any handlers left undefined will return an error. + lookup func(*pb.LookupRequest) (*pb.LookupResponse, error) + runQuery func(*pb.RunQueryRequest) (*pb.RunQueryResponse, error) + beginTransaction func(*pb.BeginTransactionRequest) (*pb.BeginTransactionResponse, error) + commit func(*pb.CommitRequest) (*pb.CommitResponse, error) + rollback func(*pb.RollbackRequest) (*pb.RollbackResponse, error) + allocateIds func(*pb.AllocateIdsRequest) (*pb.AllocateIdsResponse, error) +} + +func (c *fakeDatastoreClient) Lookup(ctx context.Context, in *pb.LookupRequest, opts ...grpc.CallOption) (*pb.LookupResponse, error) { + if c.lookup == nil { + return nil, errors.New("no lookup handler defined") + } + return c.lookup(in) +} +func (c *fakeDatastoreClient) RunQuery(ctx context.Context, in *pb.RunQueryRequest, opts ...grpc.CallOption) (*pb.RunQueryResponse, error) { + if c.runQuery == nil { + return nil, errors.New("no runQuery handler defined") + } + return c.runQuery(in) +} +func (c *fakeDatastoreClient) BeginTransaction(ctx context.Context, in *pb.BeginTransactionRequest, opts ...grpc.CallOption) (*pb.BeginTransactionResponse, error) { + if c.beginTransaction == nil { + return nil, errors.New("no beginTransaction handler defined") + } + return c.beginTransaction(in) +} +func (c *fakeDatastoreClient) Commit(ctx context.Context, in *pb.CommitRequest, opts ...grpc.CallOption) (*pb.CommitResponse, error) { + if c.commit == nil { + return nil, errors.New("no commit handler defined") + } + return c.commit(in) +} +func (c *fakeDatastoreClient) Rollback(ctx context.Context, in *pb.RollbackRequest, opts ...grpc.CallOption) (*pb.RollbackResponse, error) { + if c.rollback == nil { + return nil, errors.New("no rollback handler defined") + } + return c.rollback(in) +} +func (c *fakeDatastoreClient) AllocateIds(ctx context.Context, in *pb.AllocateIdsRequest, opts ...grpc.CallOption) (*pb.AllocateIdsResponse, error) { + if c.allocateIds == nil { + return nil, errors.New("no allocateIds handler defined") + } + return c.allocateIds(in) +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/doc.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/doc.go new file mode 100644 index 000000000..df86cd2cc --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/doc.go @@ -0,0 +1,454 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* +Package datastore provides a client for Google Cloud Datastore. + +Note: This package is in beta. Some backwards-incompatible changes may occur. + + +Basic Operations + +Entities are the unit of storage and are associated with a key. A key +consists of an optional parent key, a string application ID, a string kind +(also known as an entity type), and either a StringID or an IntID. A +StringID is also known as an entity name or key name. + +It is valid to create a key with a zero StringID and a zero IntID; this is +called an incomplete key, and does not refer to any saved entity. Putting an +entity into the datastore under an incomplete key will cause a unique key +to be generated for that entity, with a non-zero IntID. + +An entity's contents are a mapping from case-sensitive field names to values. +Valid value types are: + - signed integers (int, int8, int16, int32 and int64), + - bool, + - string, + - float32 and float64, + - []byte (up to 1 megabyte in length), + - any type whose underlying type is one of the above predeclared types, + - *Key, + - GeoPoint, + - time.Time (stored with microsecond precision), + - structs whose fields are all valid value types, + - pointers to structs whose fields are all valid value types, + - slices of any of the above. + +Slices of structs are valid, as are structs that contain slices. + +The Get and Put functions load and save an entity's contents. An entity's +contents are typically represented by a struct pointer. + +Example code: + + type Entity struct { + Value string + } + + func main() { + ctx := context.Background() + + // Create a datastore client. In a typical application, you would create + // a single client which is reused for every datastore operation. + dsClient, err := datastore.NewClient(ctx, "my-project") + if err != nil { + // Handle error. + } + + k := datastore.NameKey("Entity", "stringID", nil) + e := new(Entity) + if err := dsClient.Get(ctx, k, e); err != nil { + // Handle error. + } + + old := e.Value + e.Value = "Hello World!" + + if _, err := dsClient.Put(ctx, k, e); err != nil { + // Handle error. + } + + fmt.Printf("Updated value from %q to %q\n", old, e.Value) + } + +GetMulti, PutMulti and DeleteMulti are batch versions of the Get, Put and +Delete functions. They take a []*Key instead of a *Key, and may return a +datastore.MultiError when encountering partial failure. + + +Properties + +An entity's contents can be represented by a variety of types. These are +typically struct pointers, but can also be any type that implements the +PropertyLoadSaver interface. If using a struct pointer, you do not have to +explicitly implement the PropertyLoadSaver interface; the datastore will +automatically convert via reflection. If a struct pointer does implement that +interface then those methods will be used in preference to the default +behavior for struct pointers. Struct pointers are more strongly typed and are +easier to use; PropertyLoadSavers are more flexible. + +The actual types passed do not have to match between Get and Put calls or even +across different calls to datastore. It is valid to put a *PropertyList and +get that same entity as a *myStruct, or put a *myStruct0 and get a *myStruct1. +Conceptually, any entity is saved as a sequence of properties, and is loaded +into the destination value on a property-by-property basis. When loading into +a struct pointer, an entity that cannot be completely represented (such as a +missing field) will result in an ErrFieldMismatch error but it is up to the +caller whether this error is fatal, recoverable or ignorable. + +By default, for struct pointers, all properties are potentially indexed, and +the property name is the same as the field name (and hence must start with an +upper case letter). + +Fields may have a `datastore:"name,options"` tag. The tag name is the +property name, which must be one or more valid Go identifiers joined by ".", +but may start with a lower case letter. An empty tag name means to just use the +field name. A "-" tag name means that the datastore will ignore that field. + +The only valid options are "omitempty", "noindex" and "flatten". + +If the options include "omitempty" and the value of the field is empty, then the field will be omitted on Save. +The empty values are false, 0, any nil interface value, and any array, slice, map, or string of length zero. +Struct field values will never be empty. + +If options include "noindex" then the field will not be indexed. All fields are indexed +by default. Strings or byte slices longer than 1500 bytes cannot be indexed; +fields used to store long strings and byte slices must be tagged with "noindex" +or they will cause Put operations to fail. + +For a nested struct field, the options may also include "flatten". This indicates +that the immediate fields and any nested substruct fields of the nested struct should be +flattened. See below for examples. + +To use multiple options together, separate them by a comma. +The order does not matter. + +If the options is "" then the comma may be omitted. + +Example code: + + // A and B are renamed to a and b. + // A, C and J are not indexed. + // D's tag is equivalent to having no tag at all (E). + // I is ignored entirely by the datastore. + // J has tag information for both the datastore and json packages. + type TaggedStruct struct { + A int `datastore:"a,noindex"` + B int `datastore:"b"` + C int `datastore:",noindex"` + D int `datastore:""` + E int + I int `datastore:"-"` + J int `datastore:",noindex" json:"j"` + } + + +Key Field + +If the struct contains a *datastore.Key field tagged with the name "__key__", +its value will be ignored on Put. When reading the Entity back into the Go struct, +the field will be populated with the *datastore.Key value used to query for +the Entity. + +Example code: + + type MyEntity struct { + A int + K *datastore.Key `datastore:"__key__"` + } + + k := datastore.NameKey("Entity", "stringID", nil) + e := MyEntity{A: 12} + k, err = dsClient.Put(ctx, k, e) + if err != nil { + // Handle error. + } + + var entities []MyEntity + q := datastore.NewQuery("Entity").Filter("A =", 12).Limit(1) + _, err := dsClient.GetAll(ctx, q, &entities) + if err != nil { + // Handle error + } + + log.Println(entities[0]) + // Prints {12 /Entity,stringID} + + + +Structured Properties + +If the struct pointed to contains other structs, then the nested or embedded +structs are themselves saved as Entity values. For example, given these definitions: + + type Inner struct { + W int32 + X string + } + + type Outer struct { + I Inner + } + +then an Outer would have one property, Inner, encoded as an Entity value. + +If an outer struct is tagged "noindex" then all of its implicit flattened +fields are effectively "noindex". + +If the Inner struct contains a *Key field with the name "__key__", like so: + + type Inner struct { + W int32 + X string + K *datastore.Key `datastore:"__key__"` + } + + type Outer struct { + I Inner + } + +then the value of K will be used as the Key for Inner, represented +as an Entity value in datastore. + +If any nested struct fields should be flattened, instead of encoded as +Entity values, the nested struct field should be tagged with the "flatten" +option. For example, given the following: + + type Inner1 struct { + W int32 + X string + } + + type Inner2 struct { + Y float64 + } + + type Inner3 struct { + Z bool + } + + type Inner4 struct { + WW int + } + + type Inner5 struct { + X Inner4 + } + + type Outer struct { + A int16 + I []Inner1 `datastore:",flatten"` + J Inner2 `datastore:",flatten"` + K Inner5 `datastore:",flatten"` + Inner3 `datastore:",flatten"` + } + +an Outer's properties would be equivalent to those of: + + type OuterEquivalent struct { + A int16 + IDotW []int32 `datastore:"I.W"` + IDotX []string `datastore:"I.X"` + JDotY float64 `datastore:"J.Y"` + KDotXDotWW int `datastore:"K.X.WW"` + Z bool + } + +Note that the "flatten" option cannot be used for Entity value fields. +The server will reject any dotted field names for an Entity value. + + +The PropertyLoadSaver Interface + +An entity's contents can also be represented by any type that implements the +PropertyLoadSaver interface. This type may be a struct pointer, but it does +not have to be. The datastore package will call Load when getting the entity's +contents, and Save when putting the entity's contents. +Possible uses include deriving non-stored fields, verifying fields, or indexing +a field only if its value is positive. + +Example code: + + type CustomPropsExample struct { + I, J int + // Sum is not stored, but should always be equal to I + J. + Sum int `datastore:"-"` + } + + func (x *CustomPropsExample) Load(ps []datastore.Property) error { + // Load I and J as usual. + if err := datastore.LoadStruct(x, ps); err != nil { + return err + } + // Derive the Sum field. + x.Sum = x.I + x.J + return nil + } + + func (x *CustomPropsExample) Save() ([]datastore.Property, error) { + // Validate the Sum field. + if x.Sum != x.I + x.J { + return nil, errors.New("CustomPropsExample has inconsistent sum") + } + // Save I and J as usual. The code below is equivalent to calling + // "return datastore.SaveStruct(x)", but is done manually for + // demonstration purposes. + return []datastore.Property{ + { + Name: "I", + Value: int64(x.I), + }, + { + Name: "J", + Value: int64(x.J), + }, + }, nil + } + +The *PropertyList type implements PropertyLoadSaver, and can therefore hold an +arbitrary entity's contents. + +The KeyLoader Interface + +If a type implements the PropertyLoadSaver interface, it may +also want to implement the KeyLoader interface. +The KeyLoader interface exists to allow implementations of PropertyLoadSaver +to also load an Entity's Key into the Go type. This type may be a struct +pointer, but it does not have to be. The datastore package will call LoadKey +when getting the entity's contents, after calling Load. + +Example code: + + type WithKeyExample struct { + I int + Key *datastore.Key + } + + func (x *WithKeyExample) LoadKey(k *datastore.Key) error { + x.Key = k + return nil + } + + func (x *WithKeyExample) Load(ps []datastore.Property) error { + // Load I as usual. + return datastore.LoadStruct(x, ps) + } + + func (x *WithKeyExample) Save() ([]datastore.Property, error) { + // Save I as usual. + return datastore.SaveStruct(x) + } + +To load a Key into a struct which does not implement the PropertyLoadSaver +interface, see the "Key Field" section above. + + +Queries + +Queries retrieve entities based on their properties or key's ancestry. Running +a query yields an iterator of results: either keys or (key, entity) pairs. +Queries are re-usable and it is safe to call Query.Run from concurrent +goroutines. Iterators are not safe for concurrent use. + +Queries are immutable, and are either created by calling NewQuery, or derived +from an existing query by calling a method like Filter or Order that returns a +new query value. A query is typically constructed by calling NewQuery followed +by a chain of zero or more such methods. These methods are: + - Ancestor and Filter constrain the entities returned by running a query. + - Order affects the order in which they are returned. + - Project constrains the fields returned. + - Distinct de-duplicates projected entities. + - KeysOnly makes the iterator return only keys, not (key, entity) pairs. + - Start, End, Offset and Limit define which sub-sequence of matching entities + to return. Start and End take cursors, Offset and Limit take integers. Start + and Offset affect the first result, End and Limit affect the last result. + If both Start and Offset are set, then the offset is relative to Start. + If both End and Limit are set, then the earliest constraint wins. Limit is + relative to Start+Offset, not relative to End. As a special case, a + negative limit means unlimited. + +Example code: + + type Widget struct { + Description string + Price int + } + + func printWidgets(ctx context.Context, client *datastore.Client) { + q := datastore.NewQuery("Widget"). + Filter("Price <", 1000). + Order("-Price") + for t := client.Run(ctx, q); ; { + var x Widget + key, err := t.Next(&x) + if err == iterator.Done { + break + } + if err != nil { + // Handle error. + } + fmt.Printf("Key=%v\nWidget=%#v\n\n", key, x) + } + } + + +Transactions + +Client.RunInTransaction runs a function in a transaction. + +Example code: + + type Counter struct { + Count int + } + + func incCount(ctx context.Context, client *datastore.Client) { + var count int + key := datastore.NameKey("Counter", "singleton", nil) + _, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var x Counter + if err := tx.Get(key, &x); err != nil && err != datastore.ErrNoSuchEntity { + return err + } + x.Count++ + if _, err := tx.Put(key, &x); err != nil { + return err + } + count = x.Count + return nil + }) + if err != nil { + // Handle error. + } + // The value of count is only valid once the transaction is successful + // (RunInTransaction has returned nil). + fmt.Printf("Count=%d\n", count) + } + +Google Cloud Datastore Emulator + +This package supports the Cloud Datastore emulator, which is useful for testing and +development. Environment variables are used to indicate that datastore traffic should be +directed to the emulator instead of the production Datastore service. + +To install and set up the emulator and its environment variables, see the documentation +at https://cloud.google.com/datastore/docs/tools/datastore-emulator. + +Authentication + +See examples of authorization and authentication at +https://godoc.org/cloud.google.com/go#pkg-examples. + +*/ +package datastore // import "cloud.google.com/go/datastore" diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/errors.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/errors.go new file mode 100644 index 000000000..3077f80d3 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/errors.go @@ -0,0 +1,47 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file provides error functions for common API failure modes. + +package datastore + +import ( + "fmt" +) + +// MultiError is returned by batch operations when there are errors with +// particular elements. Errors will be in a one-to-one correspondence with +// the input elements; successful elements will have a nil entry. +type MultiError []error + +func (m MultiError) Error() string { + s, n := "", 0 + for _, e := range m { + if e != nil { + if n == 0 { + s = e.Error() + } + n++ + } + } + switch n { + case 0: + return "(0 errors)" + case 1: + return s + case 2: + return s + " (and 1 other error)" + } + return fmt.Sprintf("%s (and %d other errors)", s, n-1) +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/example_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/example_test.go new file mode 100644 index 000000000..c6f81e13b --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/example_test.go @@ -0,0 +1,545 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore_test + +import ( + "fmt" + "log" + "time" + + "cloud.google.com/go/datastore" + "golang.org/x/net/context" + "google.golang.org/api/iterator" +) + +func ExampleNewClient() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + _ = client // TODO: Use client. +} + +func ExampleClient_Get() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + type Article struct { + Title string + Description string + Body string `datastore:",noindex"` + Author *datastore.Key + PublishedAt time.Time + } + key := datastore.NameKey("Article", "articled1", nil) + article := &Article{} + if err := client.Get(ctx, key, article); err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_Put() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + type Article struct { + Title string + Description string + Body string `datastore:",noindex"` + Author *datastore.Key + PublishedAt time.Time + } + newKey := datastore.IncompleteKey("Article", nil) + _, err = client.Put(ctx, newKey, &Article{ + Title: "The title of the article", + Description: "The description of the article...", + Body: "...", + Author: datastore.NameKey("Author", "jbd", nil), + PublishedAt: time.Now(), + }) + if err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_Put_flatten() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + log.Fatal(err) + } + + type Animal struct { + Name string + Type string + Breed string + } + + type Human struct { + Name string + Height int + Pet Animal `datastore:",flatten"` + } + + newKey := datastore.IncompleteKey("Human", nil) + _, err = client.Put(ctx, newKey, &Human{ + Name: "Susan", + Height: 67, + Pet: Animal{ + Name: "Fluffy", + Type: "Cat", + Breed: "Sphynx", + }, + }) + if err != nil { + log.Fatal(err) + } +} + +func ExampleClient_Delete() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + key := datastore.NameKey("Article", "articled1", nil) + if err := client.Delete(ctx, key); err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_DeleteMulti() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + var keys []*datastore.Key + for i := 1; i <= 10; i++ { + keys = append(keys, datastore.IDKey("Article", int64(i), nil)) + } + if err := client.DeleteMulti(ctx, keys); err != nil { + // TODO: Handle error. + } +} + +type Post struct { + Title string + PublishedAt time.Time + Comments int +} + +func ExampleClient_GetMulti() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + keys := []*datastore.Key{ + datastore.NameKey("Post", "post1", nil), + datastore.NameKey("Post", "post2", nil), + datastore.NameKey("Post", "post3", nil), + } + posts := make([]Post, 3) + if err := client.GetMulti(ctx, keys, posts); err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_PutMulti_slice() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + keys := []*datastore.Key{ + datastore.NameKey("Post", "post1", nil), + datastore.NameKey("Post", "post2", nil), + } + + // PutMulti with a Post slice. + posts := []*Post{ + {Title: "Post 1", PublishedAt: time.Now()}, + {Title: "Post 2", PublishedAt: time.Now()}, + } + if _, err := client.PutMulti(ctx, keys, posts); err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_PutMulti_interfaceSlice() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + keys := []*datastore.Key{ + datastore.NameKey("Post", "post1", nil), + datastore.NameKey("Post", "post2", nil), + } + + // PutMulti with an empty interface slice. + posts := []interface{}{ + &Post{Title: "Post 1", PublishedAt: time.Now()}, + &Post{Title: "Post 2", PublishedAt: time.Now()}, + } + if _, err := client.PutMulti(ctx, keys, posts); err != nil { + // TODO: Handle error. + } +} + +func ExampleNewQuery() { + // Query for Post entities. + q := datastore.NewQuery("Post") + _ = q // TODO: Use the query with Client.Run. +} + +func ExampleNewQuery_options() { + // Query to order the posts by the number of comments they have recieved. + q := datastore.NewQuery("Post").Order("-Comments") + // Start listing from an offset and limit the results. + q = q.Offset(20).Limit(10) + _ = q // TODO: Use the query. +} + +func ExampleClient_Count() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + // Count the number of the post entities. + q := datastore.NewQuery("Post") + n, err := client.Count(ctx, q) + if err != nil { + // TODO: Handle error. + } + fmt.Printf("There are %d posts.", n) +} + +func ExampleClient_Run() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + // List the posts published since yesterday. + yesterday := time.Now().Add(-24 * time.Hour) + q := datastore.NewQuery("Post").Filter("PublishedAt >", yesterday) + it := client.Run(ctx, q) + _ = it // TODO: iterate using Next. +} + +func ExampleClient_NewTransaction() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + const retries = 3 + + // Increment a counter. + // See https://cloud.google.com/appengine/articles/sharding_counters for + // a more scalable solution. + type Counter struct { + Count int + } + + key := datastore.NameKey("counter", "CounterA", nil) + var tx *datastore.Transaction + for i := 0; i < retries; i++ { + tx, err = client.NewTransaction(ctx) + if err != nil { + break + } + + var c Counter + if err = tx.Get(key, &c); err != nil && err != datastore.ErrNoSuchEntity { + break + } + c.Count++ + if _, err = tx.Put(key, &c); err != nil { + break + } + + // Attempt to commit the transaction. If there's a conflict, try again. + if _, err = tx.Commit(); err != datastore.ErrConcurrentTransaction { + break + } + } + if err != nil { + // TODO: Handle error. + } +} + +func ExampleClient_RunInTransaction() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + + // Increment a counter. + // See https://cloud.google.com/appengine/articles/sharding_counters for + // a more scalable solution. + type Counter struct { + Count int + } + + var count int + key := datastore.NameKey("Counter", "singleton", nil) + _, err = client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var x Counter + if err := tx.Get(key, &x); err != nil && err != datastore.ErrNoSuchEntity { + return err + } + x.Count++ + if _, err := tx.Put(key, &x); err != nil { + return err + } + count = x.Count + return nil + }) + if err != nil { + // TODO: Handle error. + } + // The value of count is only valid once the transaction is successful + // (RunInTransaction has returned nil). + fmt.Printf("Count=%d\n", count) +} + +func ExampleClient_AllocateIDs() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + var keys []*datastore.Key + for i := 0; i < 10; i++ { + keys = append(keys, datastore.IncompleteKey("Article", nil)) + } + keys, err = client.AllocateIDs(ctx, keys) + if err != nil { + // TODO: Handle error. + } + _ = keys // TODO: Use keys. +} + +func ExampleKey_Encode() { + key := datastore.IDKey("Article", 1, nil) + encoded := key.Encode() + fmt.Println(encoded) + // Output: EgsKB0FydGljbGUQAQ +} + +func ExampleDecodeKey() { + const encoded = "EgsKB0FydGljbGUQAQ" + key, err := datastore.DecodeKey(encoded) + if err != nil { + // TODO: Handle error. + } + fmt.Println(key) + // Output: /Article,1 +} + +func ExampleIDKey() { + // Key with numeric ID. + k := datastore.IDKey("Article", 1, nil) + _ = k // TODO: Use key. +} + +func ExampleNameKey() { + // Key with string ID. + k := datastore.NameKey("Article", "article8", nil) + _ = k // TODO: Use key. +} + +func ExampleIncompleteKey() { + k := datastore.IncompleteKey("Article", nil) + _ = k // TODO: Use incomplete key. +} + +func ExampleClient_GetAll() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + var posts []*Post + keys, err := client.GetAll(ctx, datastore.NewQuery("Post"), &posts) + for i, key := range keys { + fmt.Println(key) + fmt.Println(posts[i]) + } +} + +func ExampleCommit_Key() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "") + if err != nil { + // TODO: Handle error. + } + var pk1, pk2 *datastore.PendingKey + // Create two posts in a single transaction. + commit, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error { + var err error + pk1, err = tx.Put(datastore.IncompleteKey("Post", nil), &Post{Title: "Post 1", PublishedAt: time.Now()}) + if err != nil { + return err + } + pk2, err = tx.Put(datastore.IncompleteKey("Post", nil), &Post{Title: "Post 2", PublishedAt: time.Now()}) + if err != nil { + return err + } + return nil + }) + if err != nil { + // TODO: Handle error. + } + // Now pk1, pk2 are valid PendingKeys. Let's convert them into real keys + // using the Commit object. + k1 := commit.Key(pk1) + k2 := commit.Key(pk2) + fmt.Println(k1, k2) +} + +func ExampleIterator_Next() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + it := client.Run(ctx, datastore.NewQuery("Post")) + for { + var p Post + key, err := it.Next(&p) + if err == iterator.Done { + break + } + if err != nil { + // TODO: Handle error. + } + fmt.Println(key, p) + } +} + +func ExampleIterator_Cursor() { + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + it := client.Run(ctx, datastore.NewQuery("Post")) + for { + var p Post + _, err := it.Next(&p) + if err == iterator.Done { + break + } + if err != nil { + // TODO: Handle error. + } + fmt.Println(p) + cursor, err := it.Cursor() + if err != nil { + // TODO: Handle error. + } + // When printed, a cursor will display as a string that can be passed + // to datastore.NewCursor. + fmt.Printf("to resume with this post, use cursor %s\n", cursor) + } +} + +func ExampleDecodeCursor() { + // See Query.Start for a fuller example of DecodeCursor. + // getCursor represents a function that returns a cursor from a previous + // iteration in string form. + cursorString := getCursor() + cursor, err := datastore.DecodeCursor(cursorString) + if err != nil { + // TODO: Handle error. + } + _ = cursor // TODO: Use the cursor with Query.Start or Query.End. +} + +func getCursor() string { return "" } + +func ExampleQuery_Start() { + // This example demonstrates how to use cursors and Query.Start + // to resume an iteration. + ctx := context.Background() + client, err := datastore.NewClient(ctx, "project-id") + if err != nil { + // TODO: Handle error. + } + // getCursor represents a function that returns a cursor from a previous + // iteration in string form. + cursorString := getCursor() + cursor, err := datastore.DecodeCursor(cursorString) + if err != nil { + // TODO: Handle error. + } + it := client.Run(ctx, datastore.NewQuery("Post").Start(cursor)) + _ = it // TODO: Use iterator. +} + +func ExampleLoadStruct() { + type Player struct { + User string + Score int + } + // Normally LoadStruct would only be used inside a custom implementation of + // PropertyLoadSaver; this is for illustrative purposes only. + props := []datastore.Property{ + {Name: "User", Value: "Alice"}, + {Name: "Score", Value: int64(97)}, + } + + var p Player + if err := datastore.LoadStruct(&p, props); err != nil { + // TODO: Handle error. + } + fmt.Println(p) + // Output: {Alice 97} +} + +func ExampleSaveStruct() { + type Player struct { + User string + Score int + } + + p := &Player{ + User: "Alice", + Score: 97, + } + props, err := datastore.SaveStruct(p) + if err != nil { + // TODO: Handle error. + } + fmt.Println(props) + // TODO(jba): make this output stable: Output: [{User Alice false} {Score 97 false}] +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/integration_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/integration_test.go new file mode 100644 index 000000000..5170206f8 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/integration_test.go @@ -0,0 +1,1040 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strings" + "sync" + "testing" + "time" + + "cloud.google.com/go/internal/testutil" + "golang.org/x/net/context" + "google.golang.org/api/iterator" + "google.golang.org/api/option" +) + +// TODO(djd): Make test entity clean up more robust: some test entities may +// be left behind if tests are aborted, the transport fails, etc. + +// suffix is a timestamp-based suffix which is appended to key names, +// particularly for the root keys of entity groups. This reduces flakiness +// when the tests are run in parallel. +var suffix = fmt.Sprintf("-t%d", time.Now().UnixNano()) + +func newClient(ctx context.Context, t *testing.T) *Client { + ts := testutil.TokenSource(ctx, ScopeDatastore) + if ts == nil { + t.Skip("Integration tests skipped. See CONTRIBUTING.md for details") + } + client, err := NewClient(ctx, testutil.ProjID(), option.WithTokenSource(ts)) + if err != nil { + t.Fatalf("NewClient: %v", err) + } + return client +} + +func TestBasics(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx, _ := context.WithTimeout(context.Background(), time.Second*20) + client := newClient(ctx, t) + defer client.Close() + + type X struct { + I int + S string + T time.Time + } + + x0 := X{66, "99", time.Now().Truncate(time.Millisecond)} + k, err := client.Put(ctx, IncompleteKey("BasicsX", nil), &x0) + if err != nil { + t.Fatalf("client.Put: %v", err) + } + x1 := X{} + err = client.Get(ctx, k, &x1) + if err != nil { + t.Errorf("client.Get: %v", err) + } + err = client.Delete(ctx, k) + if err != nil { + t.Errorf("client.Delete: %v", err) + } + if !reflect.DeepEqual(x0, x1) { + t.Errorf("compare: x0=%v, x1=%v", x0, x1) + } +} + +func TestTopLevelKeyLoaded(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + + ctx, _ := context.WithTimeout(context.Background(), time.Second*20) + client := newClient(ctx, t) + defer client.Close() + + completeKey := NameKey("EntityWithKey", "myent", nil) + + type EntityWithKey struct { + I int + S string + K *Key `datastore:"__key__"` + } + + in := &EntityWithKey{ + I: 12, + S: "abcd", + } + + k, err := client.Put(ctx, completeKey, in) + if err != nil { + t.Fatalf("client.Put: %v", err) + } + + var e EntityWithKey + err = client.Get(ctx, k, &e) + if err != nil { + t.Fatalf("client.Get: %v", err) + } + + // The two keys should be absolutely identical. + if !reflect.DeepEqual(e.K, k) { + t.Fatalf("e.K not equal to k; got %#v, want %#v", e.K, k) + } + +} + +func TestListValues(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + p0 := PropertyList{ + {Name: "L", Value: []interface{}{int64(12), "string", true}}, + } + k, err := client.Put(ctx, IncompleteKey("ListValue", nil), &p0) + if err != nil { + t.Fatalf("client.Put: %v", err) + } + var p1 PropertyList + if err := client.Get(ctx, k, &p1); err != nil { + t.Errorf("client.Get: %v", err) + } + if !reflect.DeepEqual(p0, p1) { + t.Errorf("compare:\np0=%v\np1=%#v", p0, p1) + } + if err = client.Delete(ctx, k); err != nil { + t.Errorf("client.Delete: %v", err) + } +} + +func TestGetMulti(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type X struct { + I int + } + p := NameKey("X", "x"+suffix, nil) + + cases := []struct { + key *Key + put bool + }{ + {key: NameKey("X", "item1", p), put: true}, + {key: NameKey("X", "item2", p), put: false}, + {key: NameKey("X", "item3", p), put: false}, + {key: NameKey("X", "item4", p), put: true}, + } + + var src, dst []*X + var srcKeys, dstKeys []*Key + for _, c := range cases { + dst = append(dst, &X{}) + dstKeys = append(dstKeys, c.key) + if c.put { + src = append(src, &X{}) + srcKeys = append(srcKeys, c.key) + } + } + if _, err := client.PutMulti(ctx, srcKeys, src); err != nil { + t.Error(err) + } + err := client.GetMulti(ctx, dstKeys, dst) + if err == nil { + t.Errorf("client.GetMulti got %v, expected error", err) + } + e, ok := err.(MultiError) + if !ok { + t.Errorf("client.GetMulti got %T, expected MultiError", err) + } + for i, err := range e { + got, want := err, (error)(nil) + if !cases[i].put { + got, want = err, ErrNoSuchEntity + } + if got != want { + t.Errorf("MultiError[%d] == %v, want %v", i, got, want) + } + } +} + +type Z struct { + S string + T string `datastore:",noindex"` + P []byte + K []byte `datastore:",noindex"` +} + +func (z Z) String() string { + var lens []string + v := reflect.ValueOf(z) + for i := 0; i < v.NumField(); i++ { + if l := v.Field(i).Len(); l > 0 { + lens = append(lens, fmt.Sprintf("len(%s)=%d", v.Type().Field(i).Name, l)) + } + } + return fmt.Sprintf("Z{ %s }", strings.Join(lens, ",")) +} + +func TestUnindexableValues(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + x1500 := strings.Repeat("x", 1500) + x1501 := strings.Repeat("x", 1501) + testCases := []struct { + in Z + wantErr bool + }{ + {in: Z{S: x1500}, wantErr: false}, + {in: Z{S: x1501}, wantErr: true}, + {in: Z{T: x1500}, wantErr: false}, + {in: Z{T: x1501}, wantErr: false}, + {in: Z{P: []byte(x1500)}, wantErr: false}, + {in: Z{P: []byte(x1501)}, wantErr: true}, + {in: Z{K: []byte(x1500)}, wantErr: false}, + {in: Z{K: []byte(x1501)}, wantErr: false}, + } + for _, tt := range testCases { + _, err := client.Put(ctx, IncompleteKey("BasicsZ", nil), &tt.in) + if (err != nil) != tt.wantErr { + t.Errorf("client.Put %s got err %v, want err %t", tt.in, err, tt.wantErr) + } + } +} + +func TestNilKey(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + testCases := []struct { + in K0 + wantErr bool + }{ + {in: K0{K: testKey0}, wantErr: false}, + {in: K0{}, wantErr: false}, + } + for _, tt := range testCases { + _, err := client.Put(ctx, IncompleteKey("NilKey", nil), &tt.in) + if (err != nil) != tt.wantErr { + t.Errorf("client.Put %s got err %v, want err %t", tt.in, err, tt.wantErr) + } + } +} + +type SQChild struct { + I, J int + T, U int64 +} + +type SQTestCase struct { + desc string + q *Query + wantCount int + wantSum int +} + +func testSmallQueries(t *testing.T, ctx context.Context, client *Client, parent *Key, children []*SQChild, + testCases []SQTestCase, extraTests ...func()) { + keys := make([]*Key, len(children)) + for i := range keys { + keys[i] = IncompleteKey("SQChild", parent) + } + keys, err := client.PutMulti(ctx, keys, children) + if err != nil { + t.Fatalf("client.PutMulti: %v", err) + } + defer func() { + err := client.DeleteMulti(ctx, keys) + if err != nil { + t.Errorf("client.DeleteMulti: %v", err) + } + }() + + for _, tc := range testCases { + count, err := client.Count(ctx, tc.q) + if err != nil { + t.Errorf("Count %q: %v", tc.desc, err) + continue + } + if count != tc.wantCount { + t.Errorf("Count %q: got %d want %d", tc.desc, count, tc.wantCount) + continue + } + } + + for _, tc := range testCases { + var got []SQChild + _, err := client.GetAll(ctx, tc.q, &got) + if err != nil { + t.Errorf("client.GetAll %q: %v", tc.desc, err) + continue + } + sum := 0 + for _, c := range got { + sum += c.I + c.J + } + if sum != tc.wantSum { + t.Errorf("sum %q: got %d want %d", tc.desc, sum, tc.wantSum) + continue + } + } + for _, x := range extraTests { + x() + } +} + +func TestFilters(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + parent := NameKey("SQParent", "TestFilters"+suffix, nil) + now := time.Now().Truncate(time.Millisecond).Unix() + children := []*SQChild{ + {I: 0, T: now, U: now}, + {I: 1, T: now, U: now}, + {I: 2, T: now, U: now}, + {I: 3, T: now, U: now}, + {I: 4, T: now, U: now}, + {I: 5, T: now, U: now}, + {I: 6, T: now, U: now}, + {I: 7, T: now, U: now}, + } + baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now) + testSmallQueries(t, ctx, client, parent, children, []SQTestCase{ + { + "I>1", + baseQuery.Filter("I>", 1), + 6, + 2 + 3 + 4 + 5 + 6 + 7, + }, + { + "I>2 AND I<=5", + baseQuery.Filter("I>", 2).Filter("I<=", 5), + 3, + 3 + 4 + 5, + }, + { + "I>=3 AND I<3", + baseQuery.Filter("I>=", 3).Filter("I<", 3), + 0, + 0, + }, + { + "I=4", + baseQuery.Filter("I=", 4), + 1, + 4, + }, + }, func() { + got := []*SQChild{} + want := []*SQChild{ + {I: 0, T: now, U: now}, + {I: 1, T: now, U: now}, + {I: 2, T: now, U: now}, + {I: 3, T: now, U: now}, + {I: 4, T: now, U: now}, + {I: 5, T: now, U: now}, + {I: 6, T: now, U: now}, + {I: 7, T: now, U: now}, + } + _, err := client.GetAll(ctx, baseQuery.Order("I"), &got) + if err != nil { + t.Errorf("client.GetAll: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("compare: got=%v, want=%v", got, want) + } + }, func() { + got := []*SQChild{} + want := []*SQChild{ + {I: 7, T: now, U: now}, + {I: 6, T: now, U: now}, + {I: 5, T: now, U: now}, + {I: 4, T: now, U: now}, + {I: 3, T: now, U: now}, + {I: 2, T: now, U: now}, + {I: 1, T: now, U: now}, + {I: 0, T: now, U: now}, + } + _, err := client.GetAll(ctx, baseQuery.Order("-I"), &got) + if err != nil { + t.Errorf("client.GetAll: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("compare: got=%v, want=%v", got, want) + } + }) +} + +func TestLargeQuery(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + parent := NameKey("LQParent", "TestFilters"+suffix, nil) + now := time.Now().Truncate(time.Millisecond).Unix() + + // Make a large number of children entities. + const n = 800 + children := make([]*SQChild, 0, n) + keys := make([]*Key, 0, n) + for i := 0; i < n; i++ { + children = append(children, &SQChild{I: i, T: now, U: now}) + keys = append(keys, IncompleteKey("SQChild", parent)) + } + + // Store using PutMulti in batches. + const batchSize = 500 + for i := 0; i < n; i = i + 500 { + j := i + batchSize + if j > n { + j = n + } + fullKeys, err := client.PutMulti(ctx, keys[i:j], children[i:j]) + if err != nil { + t.Fatalf("PutMulti(%d, %d): %v", i, j, err) + } + defer func() { + err := client.DeleteMulti(ctx, fullKeys) + if err != nil { + t.Errorf("client.DeleteMulti: %v", err) + } + }() + } + + q := NewQuery("SQChild").Ancestor(parent).Filter("T=", now).Order("I") + + // Wait group to allow us to run query tests in parallel below. + var wg sync.WaitGroup + + // Check we get the expected count and results for various limits/offsets. + queryTests := []struct { + limit, offset, want int + }{ + // Just limit. + {limit: 0, want: 0}, + {limit: 100, want: 100}, + {limit: 501, want: 501}, + {limit: n, want: n}, + {limit: n * 2, want: n}, + {limit: -1, want: n}, + // Just offset. + {limit: -1, offset: 100, want: n - 100}, + {limit: -1, offset: 500, want: n - 500}, + {limit: -1, offset: n, want: 0}, + // Limit and offset. + {limit: 100, offset: 100, want: 100}, + {limit: 1000, offset: 100, want: n - 100}, + {limit: 500, offset: 500, want: n - 500}, + } + for _, tt := range queryTests { + q := q.Limit(tt.limit).Offset(tt.offset) + wg.Add(1) + + go func(limit, offset, want int) { + defer wg.Done() + // Check Count returns the expected number of results. + count, err := client.Count(ctx, q) + if err != nil { + t.Errorf("client.Count(limit=%d offset=%d): %v", limit, offset, err) + return + } + if count != want { + t.Errorf("Count(limit=%d offset=%d) returned %d, want %d", limit, offset, count, want) + } + + var got []SQChild + _, err = client.GetAll(ctx, q, &got) + if err != nil { + t.Errorf("client.GetAll(limit=%d offset=%d): %v", limit, offset, err) + return + } + if len(got) != want { + t.Errorf("GetAll(limit=%d offset=%d) returned %d, want %d", limit, offset, len(got), want) + } + for i, child := range got { + if got, want := child.I, i+offset; got != want { + t.Errorf("GetAll(limit=%d offset=%d) got[%d].I == %d; want %d", limit, offset, i, got, want) + break + } + } + }(tt.limit, tt.offset, tt.want) + } + + // Also check iterator cursor behaviour. + cursorTests := []struct { + limit, offset int // Query limit and offset. + count int // The number of times to call "next" + want int // The I value of the desired element, -1 for "Done". + }{ + // No limits. + {count: 0, limit: -1, want: 0}, + {count: 5, limit: -1, want: 5}, + {count: 500, limit: -1, want: 500}, + {count: 1000, limit: -1, want: -1}, // No more results. + // Limits. + {count: 5, limit: 5, want: 5}, + {count: 500, limit: 5, want: 5}, + {count: 1000, limit: 1000, want: -1}, // No more results. + // Offsets. + {count: 0, offset: 5, limit: -1, want: 5}, + {count: 5, offset: 5, limit: -1, want: 10}, + {count: 200, offset: 500, limit: -1, want: 700}, + {count: 200, offset: 1000, limit: -1, want: -1}, // No more results. + } + for _, tt := range cursorTests { + wg.Add(1) + + go func(count, limit, offset, want int) { + defer wg.Done() + + // Run iterator through count calls to Next. + it := client.Run(ctx, q.Limit(limit).Offset(offset).KeysOnly()) + for i := 0; i < count; i++ { + _, err := it.Next(nil) + if err == iterator.Done { + break + } + if err != nil { + t.Errorf("count=%d, limit=%d, offset=%d: it.Next failed at i=%d", count, limit, offset, i) + return + } + } + + // Grab the cursor. + cursor, err := it.Cursor() + if err != nil { + t.Errorf("count=%d, limit=%d, offset=%d: it.Cursor: %v", count, limit, offset, err) + return + } + + // Make a request for the next element. + it = client.Run(ctx, q.Limit(1).Start(cursor)) + var entity SQChild + _, err = it.Next(&entity) + switch { + case want == -1: + if err != iterator.Done { + t.Errorf("count=%d, limit=%d, offset=%d: it.Next from cursor %v, want Done", count, limit, offset, err) + } + case err != nil: + t.Errorf("count=%d, limit=%d, offset=%d: it.Next from cursor: %v, want nil", count, limit, offset, err) + case entity.I != want: + t.Errorf("count=%d, limit=%d, offset=%d: got.I = %d, want %d", count, limit, offset, entity.I, want) + } + }(tt.count, tt.limit, tt.offset, tt.want) + } + + wg.Wait() +} + +func TestEventualConsistency(t *testing.T) { + // TODO(jba): either make this actually test eventual consistency, or + // delete it. Currently it behaves the same with or without the + // EventualConsistency call. + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + parent := NameKey("SQParent", "TestEventualConsistency"+suffix, nil) + now := time.Now().Truncate(time.Millisecond).Unix() + children := []*SQChild{ + {I: 0, T: now, U: now}, + {I: 1, T: now, U: now}, + {I: 2, T: now, U: now}, + } + query := NewQuery("SQChild").Ancestor(parent).Filter("T =", now).EventualConsistency() + testSmallQueries(t, ctx, client, parent, children, nil, func() { + got, err := client.Count(ctx, query) + if err != nil { + t.Fatalf("Count: %v", err) + } + if got < 0 || 3 < got { + t.Errorf("Count: got %d, want [0,3]", got) + } + }) +} + +func TestProjection(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + parent := NameKey("SQParent", "TestProjection"+suffix, nil) + now := time.Now().Truncate(time.Millisecond).Unix() + children := []*SQChild{ + {I: 1 << 0, J: 100, T: now, U: now}, + {I: 1 << 1, J: 100, T: now, U: now}, + {I: 1 << 2, J: 200, T: now, U: now}, + {I: 1 << 3, J: 300, T: now, U: now}, + {I: 1 << 4, J: 300, T: now, U: now}, + } + baseQuery := NewQuery("SQChild").Ancestor(parent).Filter("T=", now).Filter("J>", 150) + testSmallQueries(t, ctx, client, parent, children, []SQTestCase{ + { + "project", + baseQuery.Project("J"), + 3, + 200 + 300 + 300, + }, + { + "distinct", + baseQuery.Project("J").Distinct(), + 2, + 200 + 300, + }, + { + "distinct on", + baseQuery.Project("J").DistinctOn("J"), + 2, + 200 + 300, + }, + { + "project on meaningful (GD_WHEN) field", + baseQuery.Project("U"), + 3, + 0, + }, + }) +} + +func TestAllocateIDs(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + keys := make([]*Key, 5) + for i := range keys { + keys[i] = IncompleteKey("AllocID", nil) + } + keys, err := client.AllocateIDs(ctx, keys) + if err != nil { + t.Errorf("AllocID #0 failed: %v", err) + } + if want := len(keys); want != 5 { + t.Errorf("Expected to allocate 5 keys, %d keys are found", want) + } + for _, k := range keys { + if k.Incomplete() { + t.Errorf("Unexpeceted incomplete key found: %v", k) + } + } +} + +func TestGetAllWithFieldMismatch(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type Fat struct { + X, Y int + } + type Thin struct { + X int + } + + // Ancestor queries (those within an entity group) are strongly consistent + // by default, which prevents a test from being flaky. + // See https://cloud.google.com/appengine/docs/go/datastore/queries#Go_Data_consistency + // for more information. + parent := NameKey("SQParent", "TestGetAllWithFieldMismatch"+suffix, nil) + putKeys := make([]*Key, 3) + for i := range putKeys { + putKeys[i] = IDKey("GetAllThing", int64(10+i), parent) + _, err := client.Put(ctx, putKeys[i], &Fat{X: 20 + i, Y: 30 + i}) + if err != nil { + t.Fatalf("client.Put: %v", err) + } + } + + var got []Thin + want := []Thin{ + {X: 20}, + {X: 21}, + {X: 22}, + } + getKeys, err := client.GetAll(ctx, NewQuery("GetAllThing").Ancestor(parent), &got) + if len(getKeys) != 3 && !reflect.DeepEqual(getKeys, putKeys) { + t.Errorf("client.GetAll: keys differ\ngetKeys=%v\nputKeys=%v", getKeys, putKeys) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("client.GetAll: entities differ\ngot =%v\nwant=%v", got, want) + } + if _, ok := err.(*ErrFieldMismatch); !ok { + t.Errorf("client.GetAll: got err=%v, want ErrFieldMismatch", err) + } +} + +func TestKindlessQueries(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type Dee struct { + I int + Why string + } + type Dum struct { + I int + Pling string + } + + parent := NameKey("Tweedle", "tweedle"+suffix, nil) + + keys := []*Key{ + NameKey("Dee", "dee0", parent), + NameKey("Dum", "dum1", parent), + NameKey("Dum", "dum2", parent), + NameKey("Dum", "dum3", parent), + } + src := []interface{}{ + &Dee{1, "binary0001"}, + &Dum{2, "binary0010"}, + &Dum{4, "binary0100"}, + &Dum{8, "binary1000"}, + } + keys, err := client.PutMulti(ctx, keys, src) + if err != nil { + t.Fatalf("put: %v", err) + } + + testCases := []struct { + desc string + query *Query + want []int + wantErr string + }{ + { + desc: "Dee", + query: NewQuery("Dee"), + want: []int{1}, + }, + { + desc: "Doh", + query: NewQuery("Doh"), + want: nil}, + { + desc: "Dum", + query: NewQuery("Dum"), + want: []int{2, 4, 8}, + }, + { + desc: "", + query: NewQuery(""), + want: []int{1, 2, 4, 8}, + }, + { + desc: "Kindless filter", + query: NewQuery("").Filter("__key__ =", keys[2]), + want: []int{4}, + }, + { + desc: "Kindless order", + query: NewQuery("").Order("__key__"), + want: []int{1, 2, 4, 8}, + }, + { + desc: "Kindless bad filter", + query: NewQuery("").Filter("I =", 4), + wantErr: "kind is required", + }, + { + desc: "Kindless bad order", + query: NewQuery("").Order("-__key__"), + wantErr: "kind is required for all orders except __key__ ascending", + }, + } +loop: + for _, tc := range testCases { + q := tc.query.Ancestor(parent) + gotCount, err := client.Count(ctx, q) + if err != nil { + if tc.wantErr == "" || !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("count %q: err %v, want err %q", tc.desc, err, tc.wantErr) + } + continue + } + if tc.wantErr != "" { + t.Errorf("count %q: want err %q", tc.desc, tc.wantErr) + continue + } + if gotCount != len(tc.want) { + t.Errorf("count %q: got %d want %d", tc.desc, gotCount, len(tc.want)) + continue + } + var got []int + for iter := client.Run(ctx, q); ; { + var dst struct { + I int + Why, Pling string + } + _, err := iter.Next(&dst) + if err == iterator.Done { + break + } + if err != nil { + t.Errorf("iter.Next %q: %v", tc.desc, err) + continue loop + } + got = append(got, dst.I) + } + sort.Ints(got) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("elems %q: got %+v want %+v", tc.desc, got, tc.want) + continue + } + } +} + +func TestTransaction(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type Counter struct { + N int + T time.Time + } + + bangErr := errors.New("bang") + tests := []struct { + desc string + causeConflict []bool + retErr []error + want int + wantErr error + }{ + { + desc: "3 attempts, no conflicts", + causeConflict: []bool{false}, + retErr: []error{nil}, + want: 11, + }, + { + desc: "1 attempt, user error", + causeConflict: []bool{false}, + retErr: []error{bangErr}, + wantErr: bangErr, + }, + { + desc: "2 attempts, 1 conflict", + causeConflict: []bool{true, false}, + retErr: []error{nil, nil}, + want: 13, // Each conflict increments by 2. + }, + { + desc: "3 attempts, 3 conflicts", + causeConflict: []bool{true, true, true}, + retErr: []error{nil, nil, nil}, + wantErr: ErrConcurrentTransaction, + }, + } + + for i, tt := range tests { + // Put a new counter. + c := &Counter{N: 10, T: time.Now()} + key, err := client.Put(ctx, IncompleteKey("TransCounter", nil), c) + if err != nil { + t.Errorf("%s: client.Put: %v", tt.desc, err) + continue + } + defer client.Delete(ctx, key) + + // Increment the counter in a transaction. + // The test case can manually cause a conflict or return an + // error at each attempt. + var attempts int + _, err = client.RunInTransaction(ctx, func(tx *Transaction) error { + attempts++ + if attempts > len(tt.causeConflict) { + return fmt.Errorf("too many attempts. Got %d, max %d", attempts, len(tt.causeConflict)) + } + + var c Counter + if err := tx.Get(key, &c); err != nil { + return err + } + c.N++ + if _, err := tx.Put(key, &c); err != nil { + return err + } + + if tt.causeConflict[attempts-1] { + c.N += 1 + if _, err := client.Put(ctx, key, &c); err != nil { + return err + } + } + + return tt.retErr[attempts-1] + }, MaxAttempts(i)) + + // Check the error returned by RunInTransaction. + if err != tt.wantErr { + t.Errorf("%s: got err %v, want %v", tt.desc, err, tt.wantErr) + continue + } + if err != nil { + continue + } + + // Check the final value of the counter. + if err := client.Get(ctx, key, c); err != nil { + t.Errorf("%s: client.Get: %v", tt.desc, err) + continue + } + if c.N != tt.want { + t.Errorf("%s: counter N=%d, want N=%d", tt.desc, c.N, tt.want) + } + } +} + +func TestNilPointers(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type X struct { + S string + } + + src := []*X{{"zero"}, {"one"}} + keys := []*Key{IncompleteKey("NilX", nil), IncompleteKey("NilX", nil)} + keys, err := client.PutMulti(ctx, keys, src) + if err != nil { + t.Fatalf("PutMulti: %v", err) + } + + // It's okay to store into a slice of nil *X. + xs := make([]*X, 2) + if err := client.GetMulti(ctx, keys, xs); err != nil { + t.Errorf("GetMulti: %v", err) + } else if !reflect.DeepEqual(xs, src) { + t.Errorf("GetMulti fetched %v, want %v", xs, src) + } + + // It isn't okay to store into a single nil *X. + var x0 *X + if err, want := client.Get(ctx, keys[0], x0), ErrInvalidEntityType; err != want { + t.Errorf("Get: err %v; want %v", err, want) + } + + if err := client.DeleteMulti(ctx, keys); err != nil { + t.Errorf("Delete: %v", err) + } +} + +func TestNestedRepeatedElementNoIndex(t *testing.T) { + if testing.Short() { + t.Skip("Integration tests skipped in short mode") + } + ctx := context.Background() + client := newClient(ctx, t) + defer client.Close() + + type Inner struct { + Name string + Value string `datastore:",noindex"` + } + type Outer struct { + Config []Inner + } + m := &Outer{ + Config: []Inner{ + {Name: "short", Value: "a"}, + {Name: "long", Value: strings.Repeat("a", 2000)}, + }, + } + + key := NameKey("Nested", "Nested"+suffix, nil) + if _, err := client.Put(ctx, key, m); err != nil { + t.Fatalf("client.Put: %v", err) + } + if err := client.Delete(ctx, key); err != nil { + t.Fatalf("client.Delete: %v", err) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key.go new file mode 100644 index 000000000..b9f2cf5e1 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key.go @@ -0,0 +1,280 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "bytes" + "encoding/base64" + "encoding/gob" + "errors" + "strconv" + "strings" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +// Key represents the datastore key for a stored entity. +type Key struct { + // Kind cannot be empty. + Kind string + // Either ID or Name must be zero for the Key to be valid. + // If both are zero, the Key is incomplete. + ID int64 + Name string + // Parent must either be a complete Key or nil. + Parent *Key + + // Namespace provides the ability to partition your data for multiple + // tenants. In most cases, it is not necessary to specify a namespace. + // See docs on datastore multitenancy for details: + // https://cloud.google.com/datastore/docs/concepts/multitenancy + Namespace string +} + +// Incomplete reports whether the key does not refer to a stored entity. +func (k *Key) Incomplete() bool { + return k.Name == "" && k.ID == 0 +} + +// valid returns whether the key is valid. +func (k *Key) valid() bool { + if k == nil { + return false + } + for ; k != nil; k = k.Parent { + if k.Kind == "" { + return false + } + if k.Name != "" && k.ID != 0 { + return false + } + if k.Parent != nil { + if k.Parent.Incomplete() { + return false + } + if k.Parent.Namespace != k.Namespace { + return false + } + } + } + return true +} + +// Equal reports whether two keys are equal. Two keys are equal if they are +// both nil, or if their kinds, IDs, names, namespaces and parents are equal. +func (k *Key) Equal(o *Key) bool { + for { + if k == nil || o == nil { + return k == o // if either is nil, both must be nil + } + if k.Namespace != o.Namespace || k.Name != o.Name || k.ID != o.ID || k.Kind != o.Kind { + return false + } + if k.Parent == nil && o.Parent == nil { + return true + } + k = k.Parent + o = o.Parent + } +} + +// marshal marshals the key's string representation to the buffer. +func (k *Key) marshal(b *bytes.Buffer) { + if k.Parent != nil { + k.Parent.marshal(b) + } + b.WriteByte('/') + b.WriteString(k.Kind) + b.WriteByte(',') + if k.Name != "" { + b.WriteString(k.Name) + } else { + b.WriteString(strconv.FormatInt(k.ID, 10)) + } +} + +// String returns a string representation of the key. +func (k *Key) String() string { + if k == nil { + return "" + } + b := bytes.NewBuffer(make([]byte, 0, 512)) + k.marshal(b) + return b.String() +} + +// Note: Fields not renamed compared to appengine gobKey struct +// This ensures gobs created by appengine can be read here, and vice/versa +type gobKey struct { + Kind string + StringID string + IntID int64 + Parent *gobKey + AppID string + Namespace string +} + +func keyToGobKey(k *Key) *gobKey { + if k == nil { + return nil + } + return &gobKey{ + Kind: k.Kind, + StringID: k.Name, + IntID: k.ID, + Parent: keyToGobKey(k.Parent), + Namespace: k.Namespace, + } +} + +func gobKeyToKey(gk *gobKey) *Key { + if gk == nil { + return nil + } + return &Key{ + Kind: gk.Kind, + Name: gk.StringID, + ID: gk.IntID, + Parent: gobKeyToKey(gk.Parent), + Namespace: gk.Namespace, + } +} + +// GobEncode marshals the key into a sequence of bytes +// using an encoding/gob.Encoder. +func (k *Key) GobEncode() ([]byte, error) { + buf := new(bytes.Buffer) + if err := gob.NewEncoder(buf).Encode(keyToGobKey(k)); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// GobDecode unmarshals a sequence of bytes using an encoding/gob.Decoder. +func (k *Key) GobDecode(buf []byte) error { + gk := new(gobKey) + if err := gob.NewDecoder(bytes.NewBuffer(buf)).Decode(gk); err != nil { + return err + } + *k = *gobKeyToKey(gk) + return nil +} + +// MarshalJSON marshals the key into JSON. +func (k *Key) MarshalJSON() ([]byte, error) { + return []byte(`"` + k.Encode() + `"`), nil +} + +// UnmarshalJSON unmarshals a key JSON object into a Key. +func (k *Key) UnmarshalJSON(buf []byte) error { + if len(buf) < 2 || buf[0] != '"' || buf[len(buf)-1] != '"' { + return errors.New("datastore: bad JSON key") + } + k2, err := DecodeKey(string(buf[1 : len(buf)-1])) + if err != nil { + return err + } + *k = *k2 + return nil +} + +// Encode returns an opaque representation of the key +// suitable for use in HTML and URLs. +// This is compatible with the Python and Java runtimes. +func (k *Key) Encode() string { + pKey := keyToProto(k) + + b, err := proto.Marshal(pKey) + if err != nil { + panic(err) + } + + // Trailing padding is stripped. + return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") +} + +// DecodeKey decodes a key from the opaque representation returned by Encode. +func DecodeKey(encoded string) (*Key, error) { + // Re-add padding. + if m := len(encoded) % 4; m != 0 { + encoded += strings.Repeat("=", 4-m) + } + + b, err := base64.URLEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + + pKey := new(pb.Key) + if err := proto.Unmarshal(b, pKey); err != nil { + return nil, err + } + return protoToKey(pKey) +} + +// AllocateIDs accepts a slice of incomplete keys and returns a +// slice of complete keys that are guaranteed to be valid in the datastore. +func (c *Client) AllocateIDs(ctx context.Context, keys []*Key) ([]*Key, error) { + if keys == nil { + return nil, nil + } + + req := &pb.AllocateIdsRequest{ + ProjectId: c.dataset, + Keys: multiKeyToProto(keys), + } + resp, err := c.client.AllocateIds(ctx, req) + if err != nil { + return nil, err + } + + return multiProtoToKey(resp.Keys) +} + +// IncompleteKey creates a new incomplete key. +// The supplied kind cannot be empty. +// The namespace of the new key is empty. +func IncompleteKey(kind string, parent *Key) *Key { + return &Key{ + Kind: kind, + Parent: parent, + } +} + +// NameKey creates a new key with a name. +// The supplied kind cannot be empty. +// The supplied parent must either be a complete key or nil. +// The namespace of the new key is empty. +func NameKey(kind, name string, parent *Key) *Key { + return &Key{ + Kind: kind, + Name: name, + Parent: parent, + } +} + +// IDKey creates a new key with an ID. +// The supplied kind cannot be empty. +// The supplied parent must either be a complete key or nil. +// The namespace of the new key is empty. +func IDKey(kind string, id int64, parent *Key) *Key { + return &Key{ + Kind: kind, + ID: id, + Parent: parent, + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key_test.go new file mode 100644 index 000000000..5f2ddcb68 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/key_test.go @@ -0,0 +1,210 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "testing" +) + +func TestEqual(t *testing.T) { + testCases := []struct { + x, y *Key + equal bool + }{ + { + x: nil, + y: nil, + equal: true, + }, + { + x: &Key{Kind: "kindA"}, + y: &Key{Kind: "kindA"}, + equal: true, + }, + { + x: &Key{Kind: "kindA", Name: "nameA"}, + y: &Key{Kind: "kindA", Name: "nameA"}, + equal: true, + }, + { + x: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"}, + y: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"}, + equal: true, + }, + { + x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}}, + y: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}}, + equal: true, + }, + { + x: &Key{Kind: "kindA", Name: "nameA"}, + y: &Key{Kind: "kindB", Name: "nameA"}, + equal: false, + }, + { + x: &Key{Kind: "kindA", Name: "nameA"}, + y: &Key{Kind: "kindA", Name: "nameB"}, + equal: false, + }, + { + x: &Key{Kind: "kindA", Name: "nameA"}, + y: &Key{Kind: "kindA", ID: 1337}, + equal: false, + }, + { + x: &Key{Kind: "kindA", Name: "nameA"}, + y: &Key{Kind: "kindA", Name: "nameA", Namespace: "gopherspace"}, + equal: false, + }, + { + x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}}, + y: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindY", Name: "nameX"}}, + equal: false, + }, + { + x: &Key{Kind: "kindA", ID: 1337, Parent: &Key{Kind: "kindX", Name: "nameX"}}, + y: &Key{Kind: "kindA", ID: 1337}, + equal: false, + }, + } + + for _, tt := range testCases { + if got := tt.x.Equal(tt.y); got != tt.equal { + t.Errorf("Equal(%v, %v) = %t; want %t", tt.x, tt.y, got, tt.equal) + } + if got := tt.y.Equal(tt.x); got != tt.equal { + t.Errorf("Equal(%v, %v) = %t; want %t", tt.y, tt.x, got, tt.equal) + } + } +} + +func TestEncoding(t *testing.T) { + testCases := []struct { + k *Key + valid bool + }{ + { + k: nil, + valid: false, + }, + { + k: &Key{}, + valid: false, + }, + { + k: &Key{Kind: "kindA"}, + valid: true, + }, + { + k: &Key{Kind: "kindA", Namespace: "gopherspace"}, + valid: true, + }, + { + k: &Key{Kind: "kindA", Name: "nameA"}, + valid: true, + }, + { + k: &Key{Kind: "kindA", ID: 1337}, + valid: true, + }, + { + k: &Key{Kind: "kindA", Name: "nameA", ID: 1337}, + valid: false, + }, + { + k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB", Name: "nameB"}}, + valid: true, + }, + { + k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB"}}, + valid: false, + }, + { + k: &Key{Kind: "kindA", Parent: &Key{Kind: "kindB", Name: "nameB", Namespace: "gopherspace"}}, + valid: false, + }, + } + + for _, tt := range testCases { + if got := tt.k.valid(); got != tt.valid { + t.Errorf("valid(%v) = %t; want %t", tt.k, got, tt.valid) + } + + // Check encoding/decoding for valid keys. + if !tt.valid { + continue + } + enc := tt.k.Encode() + dec, err := DecodeKey(enc) + if err != nil { + t.Errorf("DecodeKey(%q) from %v: %v", enc, tt.k, err) + continue + } + if !tt.k.Equal(dec) { + t.Logf("Proto: %s", keyToProto(tt.k)) + t.Errorf("Decoded key %v not equal to %v", dec, tt.k) + } + + b, err := json.Marshal(tt.k) + if err != nil { + t.Errorf("json.Marshal(%v): %v", tt.k, err) + continue + } + key := &Key{} + if err := json.Unmarshal(b, key); err != nil { + t.Errorf("json.Unmarshal(%s) for key %v: %v", b, tt.k, err) + continue + } + if !tt.k.Equal(key) { + t.Errorf("JSON decoded key %v not equal to %v", dec, tt.k) + } + + buf := &bytes.Buffer{} + gobEnc := gob.NewEncoder(buf) + if err := gobEnc.Encode(tt.k); err != nil { + t.Errorf("gobEnc.Encode(%v): %v", tt.k, err) + continue + } + gobDec := gob.NewDecoder(buf) + key = &Key{} + if err := gobDec.Decode(key); err != nil { + t.Errorf("gobDec.Decode() for key %v: %v", tt.k, err) + } + if !tt.k.Equal(key) { + t.Errorf("gob decoded key %v not equal to %v", dec, tt.k) + } + } +} + +func TestInvalidKeyDecode(t *testing.T) { + // Check that decoding an invalid key returns an err and doesn't panic. + enc := NameKey("Kind", "Foo", nil).Encode() + + invalid := []string{ + "", + "Laboratorio", + enc + "Junk", + enc[:len(enc)-4], + } + for _, enc := range invalid { + key, err := DecodeKey(enc) + if err == nil || key != nil { + t.Errorf("DecodeKey(%q) = %v, %v; want nil, error", enc, key, err) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load.go new file mode 100644 index 000000000..03bde8102 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load.go @@ -0,0 +1,491 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "fmt" + "reflect" + "strings" + "time" + + "cloud.google.com/go/internal/fields" + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +var ( + typeOfByteSlice = reflect.TypeOf([]byte(nil)) + typeOfTime = reflect.TypeOf(time.Time{}) + typeOfGeoPoint = reflect.TypeOf(GeoPoint{}) + typeOfKeyPtr = reflect.TypeOf(&Key{}) + typeOfEntityPtr = reflect.TypeOf(&Entity{}) +) + +// typeMismatchReason returns a string explaining why the property p could not +// be stored in an entity field of type v.Type(). +func typeMismatchReason(p Property, v reflect.Value) string { + entityType := "empty" + switch p.Value.(type) { + case int64: + entityType = "int" + case bool: + entityType = "bool" + case string: + entityType = "string" + case float64: + entityType = "float" + case *Key: + entityType = "*datastore.Key" + case *Entity: + entityType = "*datastore.Entity" + case GeoPoint: + entityType = "GeoPoint" + case time.Time: + entityType = "time.Time" + case []byte: + entityType = "[]byte" + } + + return fmt.Sprintf("type mismatch: %s versus %v", entityType, v.Type()) +} + +type propertyLoader struct { + // m holds the number of times a substruct field like "Foo.Bar.Baz" has + // been seen so far. The map is constructed lazily. + m map[string]int +} + +func (l *propertyLoader) load(codec fields.List, structValue reflect.Value, p Property, prev map[string]struct{}) string { + sl, ok := p.Value.([]interface{}) + if !ok { + return l.loadOneElement(codec, structValue, p, prev) + } + for _, val := range sl { + p.Value = val + if errStr := l.loadOneElement(codec, structValue, p, prev); errStr != "" { + return errStr + } + } + return "" +} + +// loadOneElement loads the value of Property p into structValue based on the provided +// codec. codec is used to find the field in structValue into which p should be loaded. +// prev is the set of property names already seen for structValue. +func (l *propertyLoader) loadOneElement(codec fields.List, structValue reflect.Value, p Property, prev map[string]struct{}) string { + var sliceOk bool + var sliceIndex int + var v reflect.Value + + name := p.Name + fieldNames := strings.Split(name, ".") + + for len(fieldNames) > 0 { + var field *fields.Field + + // Start by trying to find a field with name. If none found, + // cut off the last field (delimited by ".") and find its parent + // in the codec. + // eg. for name "A.B.C.D", split off "A.B.C" and try to + // find a field in the codec with this name. + // Loop again with "A.B", etc. + for i := len(fieldNames); i > 0; i-- { + parent := strings.Join(fieldNames[:i], ".") + field = codec.Match(parent) + if field != nil { + fieldNames = fieldNames[i:] + break + } + } + + // If we never found a matching field in the codec, return + // error message. + if field == nil { + return "no such struct field" + } + + v = initField(structValue, field.Index) + if !v.IsValid() { + return "no such struct field" + } + if !v.CanSet() { + return "cannot set struct field" + } + + // If field implements PLS, we delegate loading to the PLS's Load early, + // and stop iterating through fields. + ok, err := plsFieldLoad(v, p, fieldNames) + if err != nil { + return err.Error() + } + if ok { + return "" + } + + if field.Type.Kind() == reflect.Struct { + codec, err = structCache.Fields(field.Type) + if err != nil { + return err.Error() + } + structValue = v + } + + // If the element is a slice, we need to accommodate it. + if v.Kind() == reflect.Slice && v.Type() != typeOfByteSlice { + if l.m == nil { + l.m = make(map[string]int) + } + sliceIndex = l.m[p.Name] + l.m[p.Name] = sliceIndex + 1 + for v.Len() <= sliceIndex { + v.Set(reflect.Append(v, reflect.New(v.Type().Elem()).Elem())) + } + structValue = v.Index(sliceIndex) + + // If structValue implements PLS, we delegate loading to the PLS's + // Load early, and stop iterating through fields. + ok, err := plsFieldLoad(structValue, p, fieldNames) + if err != nil { + return err.Error() + } + if ok { + return "" + } + + if structValue.Type().Kind() == reflect.Struct { + codec, err = structCache.Fields(structValue.Type()) + if err != nil { + return err.Error() + } + } + sliceOk = true + } + } + + var slice reflect.Value + if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 { + slice = v + v = reflect.New(v.Type().Elem()).Elem() + } else if _, ok := prev[p.Name]; ok && !sliceOk { + // Zero the field back out that was set previously, turns out + // it's a slice and we don't know what to do with it + v.Set(reflect.Zero(v.Type())) + return "multiple-valued property requires a slice field type" + } + + prev[p.Name] = struct{}{} + + if errReason := setVal(v, p); errReason != "" { + // Set the slice back to its zero value. + if slice.IsValid() { + slice.Set(reflect.Zero(slice.Type())) + } + return errReason + } + + if slice.IsValid() { + slice.Index(sliceIndex).Set(v) + } + + return "" +} + +// plsFieldLoad first tries to converts v's value to a PLS, then v's addressed +// value to a PLS. If neither succeeds, plsFieldLoad returns false for first return +// value. Otherwise, the first return value will be true. +// If v is successfully converted to a PLS, plsFieldLoad will then try to Load +// the property p into v (by way of the PLS's Load method). +// +// If the field v has been flattened, the Property's name must be altered +// before calling Load to reflect the field v. +// For example, if our original field name was "A.B.C.D", +// and at this point in iteration we had initialized the field +// corresponding to "A" and have moved into the struct, so that now +// v corresponds to the field named "B", then we want to let the +// PLS handle this field (B)'s subfields ("C", "D"), +// so we send the property to the PLS's Load, renamed to "C.D". +// +// If subfields are present, the field v has been flattened. +func plsFieldLoad(v reflect.Value, p Property, subfields []string) (ok bool, err error) { + vpls, err := plsForLoad(v) + if err != nil { + return false, err + } + + if vpls == nil { + return false, nil + } + + // If Entity, load properties as well as key. + if e, ok := p.Value.(*Entity); ok { + err = loadEntity(vpls, e) + return true, err + } + + // If flattened, we must alter the property's name to reflect + // the field v. + if len(subfields) > 0 { + p.Name = strings.Join(subfields, ".") + } + + return true, vpls.Load([]Property{p}) +} + +// setVal sets 'v' to the value of the Property 'p'. +func setVal(v reflect.Value, p Property) string { + pValue := p.Value + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + x, ok := pValue.(int64) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + if v.OverflowInt(x) { + return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type()) + } + v.SetInt(x) + case reflect.Bool: + x, ok := pValue.(bool) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + v.SetBool(x) + case reflect.String: + x, ok := pValue.(string) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + v.SetString(x) + case reflect.Float32, reflect.Float64: + x, ok := pValue.(float64) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + if v.OverflowFloat(x) { + return fmt.Sprintf("value %v overflows struct field of type %v", x, v.Type()) + } + v.SetFloat(x) + case reflect.Ptr: + // v must be either a pointer to a Key or Entity. + if v.Type() != typeOfKeyPtr && v.Type().Elem().Kind() != reflect.Struct { + return typeMismatchReason(p, v) + } + + if pValue == nil { + // If v is populated already, set it to nil. + if !v.IsNil() { + v.Set(reflect.New(v.Type()).Elem()) + } + return "" + } + + switch x := pValue.(type) { + case *Key: + if _, ok := v.Interface().(*Key); !ok { + return typeMismatchReason(p, v) + } + v.Set(reflect.ValueOf(x)) + case *Entity: + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + err := loadEntity(v.Interface(), x) + if err != nil { + return err.Error() + } + + default: + return typeMismatchReason(p, v) + } + case reflect.Struct: + switch v.Type() { + case typeOfTime: + x, ok := pValue.(time.Time) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + v.Set(reflect.ValueOf(x)) + case typeOfGeoPoint: + x, ok := pValue.(GeoPoint) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + v.Set(reflect.ValueOf(x)) + default: + ent, ok := pValue.(*Entity) + if !ok { + return typeMismatchReason(p, v) + } + err := loadEntity(v.Addr().Interface(), ent) + if err != nil { + return err.Error() + } + } + case reflect.Slice: + x, ok := pValue.([]byte) + if !ok && pValue != nil { + return typeMismatchReason(p, v) + } + if v.Type().Elem().Kind() != reflect.Uint8 { + return typeMismatchReason(p, v) + } + v.SetBytes(x) + default: + return typeMismatchReason(p, v) + } + return "" +} + +// initField is similar to reflect's Value.FieldByIndex, in that it +// returns the nested struct field corresponding to index, but it +// initialises any nil pointers encountered when traversing the structure. +func initField(val reflect.Value, index []int) reflect.Value { + for _, i := range index[:len(index)-1] { + val = val.Field(i) + if val.Kind() == reflect.Ptr { + if val.IsNil() { + val.Set(reflect.New(val.Type().Elem())) + } + val = val.Elem() + } + } + return val.Field(index[len(index)-1]) +} + +// loadEntityProto loads an EntityProto into PropertyLoadSaver or struct pointer. +func loadEntityProto(dst interface{}, src *pb.Entity) error { + ent, err := protoToEntity(src) + if err != nil { + return err + } + return loadEntity(dst, ent) +} + +func loadEntity(dst interface{}, ent *Entity) error { + if pls, ok := dst.(PropertyLoadSaver); ok { + err := pls.Load(ent.Properties) + if err != nil { + return err + } + if e, ok := dst.(KeyLoader); ok { + err = e.LoadKey(ent.Key) + } + return err + } + return loadEntityToStruct(dst, ent) +} + +func loadEntityToStruct(dst interface{}, ent *Entity) error { + pls, err := newStructPLS(dst) + if err != nil { + return err + } + // Load properties. + err = pls.Load(ent.Properties) + if err != nil { + return err + } + // Load key. + keyField := pls.codec.Match(keyFieldName) + if keyField != nil && ent.Key != nil { + pls.v.FieldByIndex(keyField.Index).Set(reflect.ValueOf(ent.Key)) + } + + return nil +} + +func (s structPLS) Load(props []Property) error { + var fieldName, errReason string + var l propertyLoader + + prev := make(map[string]struct{}) + for _, p := range props { + if errStr := l.load(s.codec, s.v, p, prev); errStr != "" { + // We don't return early, as we try to load as many properties as possible. + // It is valid to load an entity into a struct that cannot fully represent it. + // That case returns an error, but the caller is free to ignore it. + fieldName, errReason = p.Name, errStr + } + } + if errReason != "" { + return &ErrFieldMismatch{ + StructType: s.v.Type(), + FieldName: fieldName, + Reason: errReason, + } + } + return nil +} + +func protoToEntity(src *pb.Entity) (*Entity, error) { + props := make([]Property, 0, len(src.Properties)) + for name, val := range src.Properties { + v, err := propToValue(val) + if err != nil { + return nil, err + } + props = append(props, Property{ + Name: name, + Value: v, + NoIndex: val.ExcludeFromIndexes, + }) + } + var key *Key + if src.Key != nil { + // Ignore any error, since nested entity values + // are allowed to have an invalid key. + key, _ = protoToKey(src.Key) + } + + return &Entity{key, props}, nil +} + +// propToValue returns a Go value that represents the PropertyValue. For +// example, a TimestampValue becomes a time.Time. +func propToValue(v *pb.Value) (interface{}, error) { + switch v := v.ValueType.(type) { + case *pb.Value_NullValue: + return nil, nil + case *pb.Value_BooleanValue: + return v.BooleanValue, nil + case *pb.Value_IntegerValue: + return v.IntegerValue, nil + case *pb.Value_DoubleValue: + return v.DoubleValue, nil + case *pb.Value_TimestampValue: + return time.Unix(v.TimestampValue.Seconds, int64(v.TimestampValue.Nanos)), nil + case *pb.Value_KeyValue: + return protoToKey(v.KeyValue) + case *pb.Value_StringValue: + return v.StringValue, nil + case *pb.Value_BlobValue: + return []byte(v.BlobValue), nil + case *pb.Value_GeoPointValue: + return GeoPoint{Lat: v.GeoPointValue.Latitude, Lng: v.GeoPointValue.Longitude}, nil + case *pb.Value_EntityValue: + return protoToEntity(v.EntityValue) + case *pb.Value_ArrayValue: + arr := make([]interface{}, 0, len(v.ArrayValue.Values)) + for _, v := range v.ArrayValue.Values { + vv, err := propToValue(v) + if err != nil { + return nil, err + } + arr = append(arr, vv) + } + return arr, nil + default: + return nil, nil + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load_test.go new file mode 100644 index 000000000..75a660a7e --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/load_test.go @@ -0,0 +1,755 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "reflect" + "testing" + + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +type Simple struct { + I int64 +} + +type SimpleWithTag struct { + I int64 `datastore:"II"` +} + +type NestedSimpleWithTag struct { + A SimpleWithTag `datastore:"AA"` +} + +type NestedSliceOfSimple struct { + A []Simple +} + +type SimpleTwoFields struct { + S string + SS string +} + +type NestedSimpleAnonymous struct { + Simple + X string +} + +type NestedSimple struct { + A Simple + I int +} + +type NestedSimple1 struct { + A Simple + X string +} + +type NestedSimple2X struct { + AA NestedSimple + A SimpleTwoFields + S string +} + +type BDotB struct { + B string `datastore:"B.B"` +} + +type ABDotB struct { + A BDotB +} + +type MultiAnonymous struct { + Simple + SimpleTwoFields + X string +} + +func TestLoadEntityNestedLegacy(t *testing.T) { + testCases := []struct { + desc string + src *pb.Entity + want interface{} + }{ + { + desc: "nested", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "A.I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + want: &NestedSimple1{ + A: Simple{I: 2}, + X: "two", + }, + }, + { + desc: "nested with tag", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "AA.II": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + want: &NestedSimpleWithTag{ + A: SimpleWithTag{I: 2}, + }, + }, + { + desc: "nested with anonymous struct field", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + want: &NestedSimpleAnonymous{ + Simple: Simple{I: 2}, + X: "two", + }, + }, + { + desc: "nested with dotted field tag", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "A.B.B": {ValueType: &pb.Value_StringValue{StringValue: "bb"}}, + }, + }, + want: &ABDotB{ + A: BDotB{ + B: "bb", + }, + }, + }, + { + desc: "nested with multiple anonymous fields", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + "S": {ValueType: &pb.Value_StringValue{StringValue: "S"}}, + "SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}}, + "X": {ValueType: &pb.Value_StringValue{StringValue: "s"}}, + }, + }, + want: &MultiAnonymous{ + Simple: Simple{I: 3}, + SimpleTwoFields: SimpleTwoFields{S: "S", SS: "s"}, + X: "s", + }, + }, + } + + for _, tc := range testCases { + dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface() + err := loadEntityProto(dst, tc.src) + if err != nil { + t.Errorf("loadEntityProto: %s: %v", tc.desc, err) + continue + } + + if !reflect.DeepEqual(tc.want, dst) { + t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want) + } + } +} + +type WithKey struct { + X string + I int + K *Key `datastore:"__key__"` +} + +type NestedWithKey struct { + Y string + N WithKey +} + +var ( + incompleteKey = newKey("", nil) + invalidKey = newKey("s", incompleteKey) +) + +func TestLoadEntityNested(t *testing.T) { + testCases := []struct { + desc string + src *pb.Entity + want interface{} + }{ + { + desc: "nested basic", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + }, + }, + }}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 10}}, + }, + }, + want: &NestedSimple{ + A: Simple{I: 3}, + I: 10, + }, + }, + { + desc: "nested with struct tags", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "AA": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "II": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, + }, + }, + }}, + }, + }, + want: &NestedSimpleWithTag{ + A: SimpleWithTag{I: 1}, + }, + }, + { + desc: "nested 2x", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "AA": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + }, + }, + }}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 1}}, + }, + }, + }}, + "A": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "S": {ValueType: &pb.Value_StringValue{StringValue: "S"}}, + "SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}}, + }, + }, + }}, + "S": {ValueType: &pb.Value_StringValue{StringValue: "SS"}}, + }, + }, + want: &NestedSimple2X{ + AA: NestedSimple{ + A: Simple{I: 3}, + I: 1, + }, + A: SimpleTwoFields{S: "S", SS: "s"}, + S: "SS", + }, + }, + { + desc: "nested anonymous", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + "X": {ValueType: &pb.Value_StringValue{StringValue: "SomeX"}}, + }, + }, + want: &NestedSimpleAnonymous{ + Simple: Simple{I: 3}, + X: "SomeX", + }, + }, + { + desc: "nested simple with slice", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + }, + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 4}}, + }, + }, + }}, + }, + }, + }}, + }, + }, + + want: &NestedSliceOfSimple{ + A: []Simple{Simple{I: 3}, Simple{I: 4}}, + }, + }, + { + desc: "nested with multiple anonymous fields", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + "S": {ValueType: &pb.Value_StringValue{StringValue: "S"}}, + "SS": {ValueType: &pb.Value_StringValue{StringValue: "s"}}, + "X": {ValueType: &pb.Value_StringValue{StringValue: "ss"}}, + }, + }, + want: &MultiAnonymous{ + Simple: Simple{I: 3}, + SimpleTwoFields: SimpleTwoFields{S: "S", SS: "s"}, + X: "ss", + }, + }, + { + desc: "nested with dotted field tag", + src: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "B.B": {ValueType: &pb.Value_StringValue{StringValue: "bb"}}, + }, + }, + }}, + }, + }, + want: &ABDotB{ + A: BDotB{ + B: "bb", + }, + }, + }, + { + desc: "nested entity with key", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}}, + "N": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Key: keyToProto(testKey1a), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + want: &NestedWithKey{ + Y: "yyy", + N: WithKey{ + X: "two", + I: 2, + K: testKey1a, + }, + }, + }, + { + desc: "nested entity with invalid key", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}}, + "N": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Key: keyToProto(invalidKey), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + want: &NestedWithKey{ + Y: "yyy", + N: WithKey{ + X: "two", + I: 2, + K: invalidKey, + }, + }, + }, + } + + for _, tc := range testCases { + dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface() + err := loadEntityProto(dst, tc.src) + if err != nil { + t.Errorf("loadEntityProto: %s: %v", tc.desc, err) + continue + } + + if !reflect.DeepEqual(tc.want, dst) { + t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want) + } + } +} + +type NestedStructPtrs struct { + *SimpleTwoFields + Nest *SimpleTwoFields + TwiceNest *NestedSimple2 + I int +} + +type NestedSimple2 struct { + A *Simple + I int +} + +func TestAlreadyPopulatedDst(t *testing.T) { + testCases := []struct { + desc string + src *pb.Entity + dst interface{} + want interface{} + }{ + { + desc: "simple already populated, nil properties", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "I": {ValueType: &pb.Value_NullValue{}}, + }, + }, + dst: &Simple{ + I: 12, + }, + want: &Simple{}, + }, + { + desc: "nested structs already populated", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "SS": {ValueType: &pb.Value_StringValue{StringValue: "world"}}, + }, + }, + dst: &SimpleTwoFields{S: "hello" /* SS: "" */}, + want: &SimpleTwoFields{S: "hello", SS: "world"}, + }, + { + desc: "nested structs already populated, pValues nil", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "S": {ValueType: &pb.Value_NullValue{}}, + "SS": {ValueType: &pb.Value_StringValue{StringValue: "ss hello"}}, + "Nest": {ValueType: &pb.Value_NullValue{}}, + "TwiceNest": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_NullValue{}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 5}}, + }, + }, + dst: &NestedStructPtrs{ + &SimpleTwoFields{S: "hello" /* SS: "" */}, + &SimpleTwoFields{ /* S: "" */ SS: "twice hello"}, + &NestedSimple2{ + A: &Simple{I: 2}, + /* I: 0 */ + }, + 0, + }, + want: &NestedStructPtrs{ + &SimpleTwoFields{ /* S: "" */ SS: "ss hello"}, + nil, + &NestedSimple2{ + /* A: nil, */ + I: 2, + }, + 5, + }, + }, + } + + for _, tc := range testCases { + err := loadEntityProto(tc.dst, tc.src) + if err != nil { + t.Errorf("loadEntityProto: %s: %v", tc.desc, err) + continue + } + + if !reflect.DeepEqual(tc.want, tc.dst) { + t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, tc.dst, tc.want) + } + } +} + +type PLS0 struct { + A string +} + +func (p *PLS0) Load(props []Property) error { + for _, pp := range props { + if pp.Name == "A" { + p.A = pp.Value.(string) + } + } + return nil +} + +func (p *PLS0) Save() (props []Property, err error) { + return []Property{{Name: "A", Value: p.A}}, nil +} + +type KeyLoader1 struct { + A string + K *Key +} + +func (kl *KeyLoader1) Load(props []Property) error { + for _, pp := range props { + if pp.Name == "A" { + kl.A = pp.Value.(string) + } + } + return nil +} + +func (kl *KeyLoader1) Save() (props []Property, err error) { + return []Property{{Name: "A", Value: kl.A}}, nil +} + +func (kl *KeyLoader1) LoadKey(k *Key) error { + kl.K = k + return nil +} + +type KeyLoader2 struct { + B int + Key *Key +} + +func (kl *KeyLoader2) Load(props []Property) error { + for _, pp := range props { + if pp.Name == "B" { + kl.B = int(pp.Value.(int64)) + } + } + return nil +} + +func (kl *KeyLoader2) Save() (props []Property, err error) { + return []Property{{Name: "B", Value: int64(kl.B)}}, nil +} + +func (kl *KeyLoader2) LoadKey(k *Key) error { + kl.Key = k + return nil +} + +type KeyLoader3 struct { + C bool + K *Key +} + +func (kl *KeyLoader3) Load(props []Property) error { + for _, pp := range props { + if pp.Name == "C" { + kl.C = pp.Value.(bool) + } + } + return nil +} + +func (kl *KeyLoader3) Save() (props []Property, err error) { + return []Property{{Name: "C", Value: kl.C}}, nil +} + +func (kl *KeyLoader3) LoadKey(k *Key) error { + kl.K = k + return nil +} + +type KeyLoader4 struct { + PLS0 + K *Key +} + +func (kl *KeyLoader4) LoadKey(k *Key) error { + kl.K = k + return nil +} + +type NotKeyLoader struct { + A string + K *Key +} + +func (p *NotKeyLoader) Load(props []Property) error { + for _, pp := range props { + if pp.Name == "A" { + p.A = pp.Value.(string) + } + } + return nil +} + +func (p *NotKeyLoader) Save() (props []Property, err error) { + return []Property{{Name: "A", Value: p.A}}, nil +} + +type NestedKeyLoaders struct { + Two *KeyLoader2 + Three []*KeyLoader3 + Four *KeyLoader4 + PLS *NotKeyLoader +} + +func TestKeyLoader(t *testing.T) { + testCases := []struct { + desc string + src *pb.Entity + dst interface{} + want interface{} + }{ + { + desc: "simple key loader", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_StringValue{StringValue: "hello"}}, + }, + }, + dst: &KeyLoader1{}, + want: &KeyLoader1{ + A: "hello", + K: testKey0, + }, + }, + { + desc: "embedded PLS key loader", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_StringValue{StringValue: "hello"}}, + }, + }, + dst: &KeyLoader4{}, + want: &KeyLoader4{ + PLS0: PLS0{A: "hello"}, + K: testKey0, + }, + }, + { + desc: "nested key loaders", + src: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Two": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "B": {ValueType: &pb.Value_IntegerValue{IntegerValue: 12}}, + }, + Key: keyToProto(testKey1a), + }, + }}, + "Three": {ValueType: &pb.Value_ArrayValue{ + ArrayValue: &pb.ArrayValue{ + Values: []*pb.Value{ + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "C": {ValueType: &pb.Value_BooleanValue{BooleanValue: true}}, + }, + Key: keyToProto(testKey1b), + }, + }}, + {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "C": {ValueType: &pb.Value_BooleanValue{BooleanValue: false}}, + }, + Key: keyToProto(testKey0), + }, + }}, + }, + }, + }}, + "Four": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_StringValue{StringValue: "testing"}}, + }, + Key: keyToProto(testKey2a), + }, + }}, + "PLS": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "A": {ValueType: &pb.Value_StringValue{StringValue: "something"}}, + }, + + Key: keyToProto(testKey1a), + }, + }}, + }, + }, + dst: &NestedKeyLoaders{}, + want: &NestedKeyLoaders{ + Two: &KeyLoader2{B: 12, Key: testKey1a}, + Three: []*KeyLoader3{ + { + C: true, + K: testKey1b, + }, + { + C: false, + K: testKey0, + }, + }, + Four: &KeyLoader4{ + PLS0: PLS0{A: "testing"}, + K: testKey2a, + }, + PLS: &NotKeyLoader{A: "something"}, + }, + }, + } + + for _, tc := range testCases { + err := loadEntityProto(tc.dst, tc.src) + if err != nil { + t.Errorf("loadEntityProto: %s: %v", tc.desc, err) + continue + } + + if !reflect.DeepEqual(tc.want, tc.dst) { + t.Errorf("%s: compare:\ngot: %+v\nwant: %+v", tc.desc, tc.dst, tc.want) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/prop.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/prop.go new file mode 100644 index 000000000..628ccc955 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/prop.go @@ -0,0 +1,342 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "fmt" + "reflect" + "strings" + "unicode" + + "cloud.google.com/go/internal/fields" +) + +// Entities with more than this many indexed properties will not be saved. +const maxIndexedProperties = 20000 + +// []byte fields more than 1 megabyte long will not be loaded or saved. +const maxBlobLen = 1 << 20 + +// Property is a name/value pair plus some metadata. A datastore entity's +// contents are loaded and saved as a sequence of Properties. Each property +// name must be unique within an entity. +type Property struct { + // Name is the property name. + Name string + // Value is the property value. The valid types are: + // - int64 + // - bool + // - string + // - float64 + // - *Key + // - time.Time + // - GeoPoint + // - []byte (up to 1 megabyte in length) + // - *Entity (representing a nested struct) + // Value can also be: + // - []interface{} where each element is one of the above types + // This set is smaller than the set of valid struct field types that the + // datastore can load and save. A Value's type must be explicitly on + // the list above; it is not sufficient for the underlying type to be + // on that list. For example, a Value of "type myInt64 int64" is + // invalid. Smaller-width integers and floats are also invalid. Again, + // this is more restrictive than the set of valid struct field types. + // + // A Value will have an opaque type when loading entities from an index, + // such as via a projection query. Load entities into a struct instead + // of a PropertyLoadSaver when using a projection query. + // + // A Value may also be the nil interface value; this is equivalent to + // Python's None but not directly representable by a Go struct. Loading + // a nil-valued property into a struct will set that field to the zero + // value. + Value interface{} + // NoIndex is whether the datastore cannot index this property. + // If NoIndex is set to false, []byte and string values are limited to + // 1500 bytes. + NoIndex bool +} + +// An Entity is the value type for a nested struct. +// This type is only used for a Property's Value. +type Entity struct { + Key *Key + Properties []Property +} + +// PropertyLoadSaver can be converted from and to a slice of Properties. +type PropertyLoadSaver interface { + Load([]Property) error + Save() ([]Property, error) +} + +// KeyLoader can store a Key. +type KeyLoader interface { + // PropertyLoadSaver is embedded because a KeyLoader + // must also always implement PropertyLoadSaver. + PropertyLoadSaver + LoadKey(k *Key) error +} + +// PropertyList converts a []Property to implement PropertyLoadSaver. +type PropertyList []Property + +var ( + typeOfPropertyLoadSaver = reflect.TypeOf((*PropertyLoadSaver)(nil)).Elem() + typeOfPropertyList = reflect.TypeOf(PropertyList(nil)) +) + +// Load loads all of the provided properties into l. +// It does not first reset *l to an empty slice. +func (l *PropertyList) Load(p []Property) error { + *l = append(*l, p...) + return nil +} + +// Save saves all of l's properties as a slice of Properties. +func (l *PropertyList) Save() ([]Property, error) { + return *l, nil +} + +// validPropertyName returns whether name consists of one or more valid Go +// identifiers joined by ".". +func validPropertyName(name string) bool { + if name == "" { + return false + } + for _, s := range strings.Split(name, ".") { + if s == "" { + return false + } + first := true + for _, c := range s { + if first { + first = false + if c != '_' && !unicode.IsLetter(c) { + return false + } + } else { + if c != '_' && !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + } + return true +} + +// parseTag interprets datastore struct field tags +func parseTag(t reflect.StructTag) (name string, keep bool, other interface{}, err error) { + s := t.Get("datastore") + parts := strings.Split(s, ",") + if parts[0] == "-" && len(parts) == 1 { + return "", false, nil, nil + } + if parts[0] != "" && !validPropertyName(parts[0]) { + err = fmt.Errorf("datastore: struct tag has invalid property name: %q", parts[0]) + return "", false, nil, err + } + + var opts saveOpts + if len(parts) > 1 { + for _, p := range parts[1:] { + switch p { + case "flatten": + opts.flatten = true + case "omitempty": + opts.omitEmpty = true + case "noindex": + opts.noIndex = true + default: + err = fmt.Errorf("datastore: struct tag has invalid option: %q", p) + return "", false, nil, err + } + } + other = opts + } + return parts[0], true, other, nil +} + +func validateType(t reflect.Type) error { + if t.Kind() != reflect.Struct { + return fmt.Errorf("datastore: validate called with non-struct type %s", t) + } + + return validateChildType(t, "", false, false, map[reflect.Type]bool{}) +} + +// validateChildType is a recursion helper func for validateType +func validateChildType(t reflect.Type, fieldName string, flatten, prevSlice bool, prevTypes map[reflect.Type]bool) error { + if prevTypes[t] { + return nil + } + prevTypes[t] = true + + switch t.Kind() { + case reflect.Slice: + if flatten && prevSlice { + return fmt.Errorf("datastore: flattening nested structs leads to a slice of slices: field %q", fieldName) + } + return validateChildType(t.Elem(), fieldName, flatten, true, prevTypes) + case reflect.Struct: + if t == typeOfTime || t == typeOfGeoPoint { + return nil + } + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + + // If a named field is unexported, ignore it. An anonymous + // unexported field is processed, because it may contain + // exported fields, which are visible. + exported := (f.PkgPath == "") + if !exported && !f.Anonymous { + continue + } + + _, keep, other, err := parseTag(f.Tag) + // Handle error from parseTag now instead of later (in cache.Fields call). + if err != nil { + return err + } + if !keep { + continue + } + if other != nil { + opts := other.(saveOpts) + flatten = flatten || opts.flatten + } + if err := validateChildType(f.Type, f.Name, flatten, prevSlice, prevTypes); err != nil { + return err + } + } + case reflect.Ptr: + if t == typeOfKeyPtr { + return nil + } + return validateChildType(t.Elem(), fieldName, flatten, prevSlice, prevTypes) + } + return nil +} + +// isLeafType determines whether or not a type is a 'leaf type' +// and should not be recursed into, but considered one field. +func isLeafType(t reflect.Type) bool { + return t == typeOfTime || t == typeOfGeoPoint +} + +// structCache collects the structs whose fields have already been calculated. +var structCache = fields.NewCache(parseTag, validateType, isLeafType) + +// structPLS adapts a struct to be a PropertyLoadSaver. +type structPLS struct { + v reflect.Value + codec fields.List +} + +// newStructPLS returns a structPLS, which implements the +// PropertyLoadSaver interface, for the struct pointer p. +func newStructPLS(p interface{}) (*structPLS, error) { + v := reflect.ValueOf(p) + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + return nil, ErrInvalidEntityType + } + v = v.Elem() + f, err := structCache.Fields(v.Type()) + if err != nil { + return nil, err + } + return &structPLS{v, f}, nil +} + +// LoadStruct loads the properties from p to dst. +// dst must be a struct pointer. +// +// The values of dst's unmatched struct fields are not modified, +// and matching slice-typed fields are not reset before appending to +// them. In particular, it is recommended to pass a pointer to a zero +// valued struct on each LoadStruct call. +func LoadStruct(dst interface{}, p []Property) error { + x, err := newStructPLS(dst) + if err != nil { + return err + } + return x.Load(p) +} + +// SaveStruct returns the properties from src as a slice of Properties. +// src must be a struct pointer. +func SaveStruct(src interface{}) ([]Property, error) { + x, err := newStructPLS(src) + if err != nil { + return nil, err + } + return x.Save() +} + +// plsForLoad tries to convert v to a PropertyLoadSaver. +// If successful, plsForLoad returns a settable v as a PropertyLoadSaver. +// +// plsForLoad is intended to be used with nested struct fields which +// may implement PropertyLoadSaver. +// +// v must be settable. +func plsForLoad(v reflect.Value) (PropertyLoadSaver, error) { + var nilPtr bool + if v.Kind() == reflect.Ptr && v.IsNil() { + nilPtr = true + v.Set(reflect.New(v.Type().Elem())) + } + + vpls, err := pls(v) + if nilPtr && (vpls == nil || err != nil) { + // unset v + v.Set(reflect.Zero(v.Type())) + } + + return vpls, err +} + +// plsForSave tries to convert v to a PropertyLoadSaver. +// If successful, plsForSave returns v as a PropertyLoadSaver. +// +// plsForSave is intended to be used with nested struct fields which +// may implement PropertyLoadSaver. +// +// v must be settable. +func plsForSave(v reflect.Value) (PropertyLoadSaver, error) { + switch v.Kind() { + case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface, reflect.Chan, reflect.Func: + // If v is nil, return early. v contains no data to save. + if v.IsNil() { + return nil, nil + } + } + + return pls(v) +} + +func pls(v reflect.Value) (PropertyLoadSaver, error) { + if v.Kind() != reflect.Ptr { + if _, ok := v.Interface().(PropertyLoadSaver); ok { + return nil, fmt.Errorf("datastore: PropertyLoadSaver methods must be implemented on a pointer to %T.", v.Interface()) + } + + v = v.Addr() + } + + vpls, _ := v.Interface().(PropertyLoadSaver) + return vpls, nil +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query.go new file mode 100644 index 000000000..09f083040 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query.go @@ -0,0 +1,773 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "encoding/base64" + "errors" + "fmt" + "math" + "reflect" + "strconv" + "strings" + + wrapperspb "github.com/golang/protobuf/ptypes/wrappers" + "golang.org/x/net/context" + "google.golang.org/api/iterator" + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +type operator int + +const ( + lessThan operator = iota + 1 + lessEq + equal + greaterEq + greaterThan + + keyFieldName = "__key__" +) + +var operatorToProto = map[operator]pb.PropertyFilter_Operator{ + lessThan: pb.PropertyFilter_LESS_THAN, + lessEq: pb.PropertyFilter_LESS_THAN_OR_EQUAL, + equal: pb.PropertyFilter_EQUAL, + greaterEq: pb.PropertyFilter_GREATER_THAN_OR_EQUAL, + greaterThan: pb.PropertyFilter_GREATER_THAN, +} + +// filter is a conditional filter on query results. +type filter struct { + FieldName string + Op operator + Value interface{} +} + +type sortDirection bool + +const ( + ascending sortDirection = false + descending sortDirection = true +) + +var sortDirectionToProto = map[sortDirection]pb.PropertyOrder_Direction{ + ascending: pb.PropertyOrder_ASCENDING, + descending: pb.PropertyOrder_DESCENDING, +} + +// order is a sort order on query results. +type order struct { + FieldName string + Direction sortDirection +} + +// NewQuery creates a new Query for a specific entity kind. +// +// An empty kind means to return all entities, including entities created and +// managed by other App Engine features, and is called a kindless query. +// Kindless queries cannot include filters or sort orders on property values. +func NewQuery(kind string) *Query { + return &Query{ + kind: kind, + limit: -1, + } +} + +// Query represents a datastore query. +type Query struct { + kind string + ancestor *Key + filter []filter + order []order + projection []string + + distinct bool + distinctOn []string + keysOnly bool + eventual bool + limit int32 + offset int32 + start []byte + end []byte + + namespace string + + trans *Transaction + + err error +} + +func (q *Query) clone() *Query { + x := *q + // Copy the contents of the slice-typed fields to a new backing store. + if len(q.filter) > 0 { + x.filter = make([]filter, len(q.filter)) + copy(x.filter, q.filter) + } + if len(q.order) > 0 { + x.order = make([]order, len(q.order)) + copy(x.order, q.order) + } + return &x +} + +// Ancestor returns a derivative query with an ancestor filter. +// The ancestor should not be nil. +func (q *Query) Ancestor(ancestor *Key) *Query { + q = q.clone() + if ancestor == nil { + q.err = errors.New("datastore: nil query ancestor") + return q + } + q.ancestor = ancestor + return q +} + +// EventualConsistency returns a derivative query that returns eventually +// consistent results. +// It only has an effect on ancestor queries. +func (q *Query) EventualConsistency() *Query { + q = q.clone() + q.eventual = true + return q +} + +// Namespace returns a derivative query that is associated with the given +// namespace. +// +// A namespace may be used to partition data for multi-tenant applications. +// For details, see https://cloud.google.com/datastore/docs/concepts/multitenancy. +func (q *Query) Namespace(ns string) *Query { + q = q.clone() + q.namespace = ns + return q +} + +// Transaction returns a derivative query that is associated with the given +// transaction. +// +// All reads performed as part of the transaction will come from a single +// consistent snapshot. Furthermore, if the transaction is set to a +// serializable isolation level, another transaction cannot concurrently modify +// the data that is read or modified by this transaction. +func (q *Query) Transaction(t *Transaction) *Query { + q = q.clone() + q.trans = t + return q +} + +// Filter returns a derivative query with a field-based filter. +// The filterStr argument must be a field name followed by optional space, +// followed by an operator, one of ">", "<", ">=", "<=", or "=". +// Fields are compared against the provided value using the operator. +// Multiple filters are AND'ed together. +// Field names which contain spaces, quote marks, or operator characters +// should be passed as quoted Go string literals as returned by strconv.Quote +// or the fmt package's %q verb. +func (q *Query) Filter(filterStr string, value interface{}) *Query { + q = q.clone() + filterStr = strings.TrimSpace(filterStr) + if filterStr == "" { + q.err = fmt.Errorf("datastore: invalid filter %q", filterStr) + return q + } + f := filter{ + FieldName: strings.TrimRight(filterStr, " ><=!"), + Value: value, + } + switch op := strings.TrimSpace(filterStr[len(f.FieldName):]); op { + case "<=": + f.Op = lessEq + case ">=": + f.Op = greaterEq + case "<": + f.Op = lessThan + case ">": + f.Op = greaterThan + case "=": + f.Op = equal + default: + q.err = fmt.Errorf("datastore: invalid operator %q in filter %q", op, filterStr) + return q + } + var err error + f.FieldName, err = unquote(f.FieldName) + if err != nil { + q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", f.FieldName) + return q + } + q.filter = append(q.filter, f) + return q +} + +// Order returns a derivative query with a field-based sort order. Orders are +// applied in the order they are added. The default order is ascending; to sort +// in descending order prefix the fieldName with a minus sign (-). +// Field names which contain spaces, quote marks, or the minus sign +// should be passed as quoted Go string literals as returned by strconv.Quote +// or the fmt package's %q verb. +func (q *Query) Order(fieldName string) *Query { + q = q.clone() + fieldName, dir := strings.TrimSpace(fieldName), ascending + if strings.HasPrefix(fieldName, "-") { + fieldName, dir = strings.TrimSpace(fieldName[1:]), descending + } else if strings.HasPrefix(fieldName, "+") { + q.err = fmt.Errorf("datastore: invalid order: %q", fieldName) + return q + } + fieldName, err := unquote(fieldName) + if err != nil { + q.err = fmt.Errorf("datastore: invalid syntax for quoted field name %q", fieldName) + return q + } + if fieldName == "" { + q.err = errors.New("datastore: empty order") + return q + } + q.order = append(q.order, order{ + Direction: dir, + FieldName: fieldName, + }) + return q +} + +// unquote optionally interprets s as a double-quoted or backquoted Go +// string literal if it begins with the relevant character. +func unquote(s string) (string, error) { + if s == "" || (s[0] != '`' && s[0] != '"') { + return s, nil + } + return strconv.Unquote(s) +} + +// Project returns a derivative query that yields only the given fields. It +// cannot be used with KeysOnly. +func (q *Query) Project(fieldNames ...string) *Query { + q = q.clone() + q.projection = append([]string(nil), fieldNames...) + return q +} + +// Distinct returns a derivative query that yields de-duplicated entities with +// respect to the set of projected fields. It is only used for projection +// queries. Distinct cannot be used with DistinctOn. +func (q *Query) Distinct() *Query { + q = q.clone() + q.distinct = true + return q +} + +// DistinctOn returns a derivative query that yields de-duplicated entities with +// respect to the set of the specified fields. It is only used for projection +// queries. The field list should be a subset of the projected field list. +// DistinctOn cannot be used with Distinct. +func (q *Query) DistinctOn(fieldNames ...string) *Query { + q = q.clone() + q.distinctOn = fieldNames + return q +} + +// KeysOnly returns a derivative query that yields only keys, not keys and +// entities. It cannot be used with projection queries. +func (q *Query) KeysOnly() *Query { + q = q.clone() + q.keysOnly = true + return q +} + +// Limit returns a derivative query that has a limit on the number of results +// returned. A negative value means unlimited. +func (q *Query) Limit(limit int) *Query { + q = q.clone() + if limit < math.MinInt32 || limit > math.MaxInt32 { + q.err = errors.New("datastore: query limit overflow") + return q + } + q.limit = int32(limit) + return q +} + +// Offset returns a derivative query that has an offset of how many keys to +// skip over before returning results. A negative value is invalid. +func (q *Query) Offset(offset int) *Query { + q = q.clone() + if offset < 0 { + q.err = errors.New("datastore: negative query offset") + return q + } + if offset > math.MaxInt32 { + q.err = errors.New("datastore: query offset overflow") + return q + } + q.offset = int32(offset) + return q +} + +// Start returns a derivative query with the given start point. +func (q *Query) Start(c Cursor) *Query { + q = q.clone() + q.start = c.cc + return q +} + +// End returns a derivative query with the given end point. +func (q *Query) End(c Cursor) *Query { + q = q.clone() + q.end = c.cc + return q +} + +// toProto converts the query to a protocol buffer. +func (q *Query) toProto(req *pb.RunQueryRequest) error { + if len(q.projection) != 0 && q.keysOnly { + return errors.New("datastore: query cannot both project and be keys-only") + } + if len(q.distinctOn) != 0 && q.distinct { + return errors.New("datastore: query cannot be both distinct and distinct-on") + } + dst := &pb.Query{} + if q.kind != "" { + dst.Kind = []*pb.KindExpression{{Name: q.kind}} + } + if q.projection != nil { + for _, propertyName := range q.projection { + dst.Projection = append(dst.Projection, &pb.Projection{Property: &pb.PropertyReference{Name: propertyName}}) + } + + for _, propertyName := range q.distinctOn { + dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName}) + } + + if q.distinct { + for _, propertyName := range q.projection { + dst.DistinctOn = append(dst.DistinctOn, &pb.PropertyReference{Name: propertyName}) + } + } + } + if q.keysOnly { + dst.Projection = []*pb.Projection{{Property: &pb.PropertyReference{Name: keyFieldName}}} + } + + var filters []*pb.Filter + for _, qf := range q.filter { + if qf.FieldName == "" { + return errors.New("datastore: empty query filter field name") + } + v, err := interfaceToProto(reflect.ValueOf(qf.Value).Interface(), false) + if err != nil { + return fmt.Errorf("datastore: bad query filter value type: %v", err) + } + op, ok := operatorToProto[qf.Op] + if !ok { + return errors.New("datastore: unknown query filter operator") + } + xf := &pb.PropertyFilter{ + Op: op, + Property: &pb.PropertyReference{Name: qf.FieldName}, + Value: v, + } + filters = append(filters, &pb.Filter{ + FilterType: &pb.Filter_PropertyFilter{PropertyFilter: xf}, + }) + } + + if q.ancestor != nil { + filters = append(filters, &pb.Filter{ + FilterType: &pb.Filter_PropertyFilter{PropertyFilter: &pb.PropertyFilter{ + Property: &pb.PropertyReference{Name: keyFieldName}, + Op: pb.PropertyFilter_HAS_ANCESTOR, + Value: &pb.Value{ValueType: &pb.Value_KeyValue{KeyValue: keyToProto(q.ancestor)}}, + }}}) + } + + if len(filters) == 1 { + dst.Filter = filters[0] + } else if len(filters) > 1 { + dst.Filter = &pb.Filter{FilterType: &pb.Filter_CompositeFilter{CompositeFilter: &pb.CompositeFilter{ + Op: pb.CompositeFilter_AND, + Filters: filters, + }}} + } + + for _, qo := range q.order { + if qo.FieldName == "" { + return errors.New("datastore: empty query order field name") + } + xo := &pb.PropertyOrder{ + Property: &pb.PropertyReference{Name: qo.FieldName}, + Direction: sortDirectionToProto[qo.Direction], + } + dst.Order = append(dst.Order, xo) + } + if q.limit >= 0 { + dst.Limit = &wrapperspb.Int32Value{Value: q.limit} + } + dst.Offset = q.offset + dst.StartCursor = q.start + dst.EndCursor = q.end + + if t := q.trans; t != nil { + if t.id == nil { + return errExpiredTransaction + } + if q.eventual { + return errors.New("datastore: cannot use EventualConsistency query in a transaction") + } + req.ReadOptions = &pb.ReadOptions{ + ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id}, + } + } + + if q.eventual { + req.ReadOptions = &pb.ReadOptions{ConsistencyType: &pb.ReadOptions_ReadConsistency_{ReadConsistency: pb.ReadOptions_EVENTUAL}} + } + + req.QueryType = &pb.RunQueryRequest_Query{Query: dst} + return nil +} + +// Count returns the number of results for the given query. +// +// The running time and number of API calls made by Count scale linearly with +// with the sum of the query's offset and limit. Unless the result count is +// expected to be small, it is best to specify a limit; otherwise Count will +// continue until it finishes counting or the provided context expires. +func (c *Client) Count(ctx context.Context, q *Query) (int, error) { + // Check that the query is well-formed. + if q.err != nil { + return 0, q.err + } + + // Create a copy of the query, with keysOnly true (if we're not a projection, + // since the two are incompatible). + newQ := q.clone() + newQ.keysOnly = len(newQ.projection) == 0 + + // Create an iterator and use it to walk through the batches of results + // directly. + it := c.Run(ctx, newQ) + n := 0 + for { + err := it.nextBatch() + if err == iterator.Done { + return n, nil + } + if err != nil { + return 0, err + } + n += len(it.results) + } +} + +// GetAll runs the provided query in the given context and returns all keys +// that match that query, as well as appending the values to dst. +// +// dst must have type *[]S or *[]*S or *[]P, for some struct type S or some non- +// interface, non-pointer type P such that P or *P implements PropertyLoadSaver. +// +// As a special case, *PropertyList is an invalid type for dst, even though a +// PropertyList is a slice of structs. It is treated as invalid to avoid being +// mistakenly passed when *[]PropertyList was intended. +// +// The keys returned by GetAll will be in a 1-1 correspondence with the entities +// added to dst. +// +// If q is a ``keys-only'' query, GetAll ignores dst and only returns the keys. +// +// The running time and number of API calls made by GetAll scale linearly with +// with the sum of the query's offset and limit. Unless the result count is +// expected to be small, it is best to specify a limit; otherwise GetAll will +// continue until it finishes collecting results or the provided context +// expires. +func (c *Client) GetAll(ctx context.Context, q *Query, dst interface{}) ([]*Key, error) { + var ( + dv reflect.Value + mat multiArgType + elemType reflect.Type + errFieldMismatch error + ) + if !q.keysOnly { + dv = reflect.ValueOf(dst) + if dv.Kind() != reflect.Ptr || dv.IsNil() { + return nil, ErrInvalidEntityType + } + dv = dv.Elem() + mat, elemType = checkMultiArg(dv) + if mat == multiArgTypeInvalid || mat == multiArgTypeInterface { + return nil, ErrInvalidEntityType + } + } + + var keys []*Key + for t := c.Run(ctx, q); ; { + k, e, err := t.next() + if err == iterator.Done { + break + } + if err != nil { + return keys, err + } + if !q.keysOnly { + ev := reflect.New(elemType) + if elemType.Kind() == reflect.Map { + // This is a special case. The zero values of a map type are + // not immediately useful; they have to be make'd. + // + // Funcs and channels are similar, in that a zero value is not useful, + // but even a freshly make'd channel isn't useful: there's no fixed + // channel buffer size that is always going to be large enough, and + // there's no goroutine to drain the other end. Theoretically, these + // types could be supported, for example by sniffing for a constructor + // method or requiring prior registration, but for now it's not a + // frequent enough concern to be worth it. Programmers can work around + // it by explicitly using Iterator.Next instead of the Query.GetAll + // convenience method. + x := reflect.MakeMap(elemType) + ev.Elem().Set(x) + } + if err = loadEntityProto(ev.Interface(), e); err != nil { + if _, ok := err.(*ErrFieldMismatch); ok { + // We continue loading entities even in the face of field mismatch errors. + // If we encounter any other error, that other error is returned. Otherwise, + // an ErrFieldMismatch is returned. + errFieldMismatch = err + } else { + return keys, err + } + } + if mat != multiArgTypeStructPtr { + ev = ev.Elem() + } + dv.Set(reflect.Append(dv, ev)) + } + keys = append(keys, k) + } + return keys, errFieldMismatch +} + +// Run runs the given query in the given context. +func (c *Client) Run(ctx context.Context, q *Query) *Iterator { + if q.err != nil { + return &Iterator{err: q.err} + } + t := &Iterator{ + ctx: ctx, + client: c, + limit: q.limit, + offset: q.offset, + keysOnly: q.keysOnly, + pageCursor: q.start, + entityCursor: q.start, + req: &pb.RunQueryRequest{ + ProjectId: c.dataset, + }, + } + if q.namespace != "" { + t.req.PartitionId = &pb.PartitionId{ + NamespaceId: q.namespace, + } + } + + if err := q.toProto(t.req); err != nil { + t.err = err + } + return t +} + +// Iterator is the result of running a query. +type Iterator struct { + ctx context.Context + client *Client + err error + + // results is the list of EntityResults still to be iterated over from the + // most recent API call. It will be nil if no requests have yet been issued. + results []*pb.EntityResult + // req is the request to send. It may be modified and used multiple times. + req *pb.RunQueryRequest + + // limit is the limit on the number of results this iterator should return. + // The zero value is used to prevent further fetches from the server. + // A negative value means unlimited. + limit int32 + // offset is the number of results that still need to be skipped. + offset int32 + // keysOnly records whether the query was keys-only (skip entity loading). + keysOnly bool + + // pageCursor is the compiled cursor for the next batch/page of result. + // TODO(djd): Can we delete this in favour of paging with the last + // entityCursor from each batch? + pageCursor []byte + // entityCursor is the compiled cursor of the next result. + entityCursor []byte +} + +// Next returns the key of the next result. When there are no more results, +// iterator.Done is returned as the error. +// +// If the query is not keys only and dst is non-nil, it also loads the entity +// stored for that key into the struct pointer or PropertyLoadSaver dst, with +// the same semantics and possible errors as for the Get function. +func (t *Iterator) Next(dst interface{}) (*Key, error) { + k, e, err := t.next() + if err != nil { + return nil, err + } + if dst != nil && !t.keysOnly { + err = loadEntityProto(dst, e) + } + return k, err +} + +func (t *Iterator) next() (*Key, *pb.Entity, error) { + // Fetch additional batches while there are no more results. + for t.err == nil && len(t.results) == 0 { + t.err = t.nextBatch() + } + if t.err != nil { + return nil, nil, t.err + } + + // Extract the next result, update cursors, and parse the entity's key. + e := t.results[0] + t.results = t.results[1:] + t.entityCursor = e.Cursor + if len(t.results) == 0 { + t.entityCursor = t.pageCursor // At the end of the batch. + } + if e.Entity.Key == nil { + return nil, nil, errors.New("datastore: internal error: server did not return a key") + } + k, err := protoToKey(e.Entity.Key) + if err != nil || k.Incomplete() { + return nil, nil, errors.New("datastore: internal error: server returned an invalid key") + } + + return k, e.Entity, nil +} + +// nextBatch makes a single call to the server for a batch of results. +func (t *Iterator) nextBatch() error { + if t.limit == 0 { + return iterator.Done // Short-circuits the zero-item response. + } + + // Adjust the query with the latest start cursor, limit and offset. + q := t.req.GetQuery() + q.StartCursor = t.pageCursor + q.Offset = t.offset + if t.limit >= 0 { + q.Limit = &wrapperspb.Int32Value{Value: t.limit} + } else { + q.Limit = nil + } + + // Run the query. + resp, err := t.client.client.RunQuery(t.ctx, t.req) + if err != nil { + return err + } + + // Adjust any offset from skipped results. + skip := resp.Batch.SkippedResults + if skip < 0 { + return errors.New("datastore: internal error: negative number of skipped_results") + } + t.offset -= skip + if t.offset < 0 { + return errors.New("datastore: internal error: query skipped too many results") + } + if t.offset > 0 && len(resp.Batch.EntityResults) > 0 { + return errors.New("datastore: internal error: query returned results before requested offset") + } + + // Adjust the limit. + if t.limit >= 0 { + t.limit -= int32(len(resp.Batch.EntityResults)) + if t.limit < 0 { + return errors.New("datastore: internal error: query returned more results than the limit") + } + } + + // If there are no more results available, set limit to zero to prevent + // further fetches. Otherwise, check that there is a next page cursor available. + if resp.Batch.MoreResults != pb.QueryResultBatch_NOT_FINISHED { + t.limit = 0 + } else if resp.Batch.EndCursor == nil { + return errors.New("datastore: internal error: server did not return a cursor") + } + + // Update cursors. + // If any results were skipped, use the SkippedCursor as the next entity cursor. + if skip > 0 { + t.entityCursor = resp.Batch.SkippedCursor + } else { + t.entityCursor = q.StartCursor + } + t.pageCursor = resp.Batch.EndCursor + + t.results = resp.Batch.EntityResults + return nil +} + +// Cursor returns a cursor for the iterator's current location. +func (t *Iterator) Cursor() (Cursor, error) { + // If there is still an offset, we need to the skip those results first. + for t.err == nil && t.offset > 0 { + t.err = t.nextBatch() + } + + if t.err != nil && t.err != iterator.Done { + return Cursor{}, t.err + } + + return Cursor{t.entityCursor}, nil +} + +// Cursor is an iterator's position. It can be converted to and from an opaque +// string. A cursor can be used from different HTTP requests, but only with a +// query with the same kind, ancestor, filter and order constraints. +// +// The zero Cursor can be used to indicate that there is no start and/or end +// constraint for a query. +type Cursor struct { + cc []byte +} + +// String returns a base-64 string representation of a cursor. +func (c Cursor) String() string { + if c.cc == nil { + return "" + } + + return strings.TrimRight(base64.URLEncoding.EncodeToString(c.cc), "=") +} + +// Decode decodes a cursor from its base-64 string representation. +func DecodeCursor(s string) (Cursor, error) { + if s == "" { + return Cursor{}, nil + } + if n := len(s) % 4; n != 0 { + s += strings.Repeat("=", 4-n) + } + b, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return Cursor{}, err + } + return Cursor{b}, nil +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query_test.go new file mode 100644 index 000000000..8e9ff45e0 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/query_test.go @@ -0,0 +1,544 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "errors" + "fmt" + "reflect" + "sort" + "testing" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + pb "google.golang.org/genproto/googleapis/datastore/v1" + "google.golang.org/grpc" +) + +var ( + key1 = &pb.Key{ + Path: []*pb.Key_PathElement{ + { + Kind: "Gopher", + IdType: &pb.Key_PathElement_Id{Id: 6}, + }, + }, + } + key2 = &pb.Key{ + Path: []*pb.Key_PathElement{ + { + Kind: "Gopher", + IdType: &pb.Key_PathElement_Id{Id: 6}, + }, + { + Kind: "Gopher", + IdType: &pb.Key_PathElement_Id{Id: 8}, + }, + }, + } +) + +type fakeClient struct { + pb.DatastoreClient + queryFn func(*pb.RunQueryRequest) (*pb.RunQueryResponse, error) + commitFn func(*pb.CommitRequest) (*pb.CommitResponse, error) +} + +func (c *fakeClient) RunQuery(_ context.Context, req *pb.RunQueryRequest, _ ...grpc.CallOption) (*pb.RunQueryResponse, error) { + return c.queryFn(req) +} + +func (c *fakeClient) Commit(_ context.Context, req *pb.CommitRequest, _ ...grpc.CallOption) (*pb.CommitResponse, error) { + return c.commitFn(req) +} + +func fakeRunQuery(in *pb.RunQueryRequest) (*pb.RunQueryResponse, error) { + expectedIn := &pb.RunQueryRequest{ + QueryType: &pb.RunQueryRequest_Query{Query: &pb.Query{ + Kind: []*pb.KindExpression{{Name: "Gopher"}}, + }}, + } + if !proto.Equal(in, expectedIn) { + return nil, fmt.Errorf("unsupported argument: got %v want %v", in, expectedIn) + } + return &pb.RunQueryResponse{ + Batch: &pb.QueryResultBatch{ + MoreResults: pb.QueryResultBatch_NO_MORE_RESULTS, + EntityResultType: pb.EntityResult_FULL, + EntityResults: []*pb.EntityResult{ + { + Entity: &pb.Entity{ + Key: key1, + Properties: map[string]*pb.Value{ + "Name": {ValueType: &pb.Value_StringValue{StringValue: "George"}}, + "Height": {ValueType: &pb.Value_IntegerValue{IntegerValue: 32}}, + }, + }, + }, + { + Entity: &pb.Entity{ + Key: key2, + Properties: map[string]*pb.Value{ + "Name": {ValueType: &pb.Value_StringValue{StringValue: "Rufus"}}, + // No height for Rufus. + }, + }, + }, + }, + }, + }, nil +} + +type StructThatImplementsPLS struct{} + +func (StructThatImplementsPLS) Load(p []Property) error { return nil } +func (StructThatImplementsPLS) Save() ([]Property, error) { return nil, nil } + +var _ PropertyLoadSaver = StructThatImplementsPLS{} + +type StructPtrThatImplementsPLS struct{} + +func (*StructPtrThatImplementsPLS) Load(p []Property) error { return nil } +func (*StructPtrThatImplementsPLS) Save() ([]Property, error) { return nil, nil } + +var _ PropertyLoadSaver = &StructPtrThatImplementsPLS{} + +type PropertyMap map[string]Property + +func (m PropertyMap) Load(props []Property) error { + for _, p := range props { + m[p.Name] = p + } + return nil +} + +func (m PropertyMap) Save() ([]Property, error) { + props := make([]Property, 0, len(m)) + for _, p := range m { + props = append(props, p) + } + return props, nil +} + +var _ PropertyLoadSaver = PropertyMap{} + +type Gopher struct { + Name string + Height int +} + +// typeOfEmptyInterface is the type of interface{}, but we can't use +// reflect.TypeOf((interface{})(nil)) directly because TypeOf takes an +// interface{}. +var typeOfEmptyInterface = reflect.TypeOf((*interface{})(nil)).Elem() + +func TestCheckMultiArg(t *testing.T) { + testCases := []struct { + v interface{} + mat multiArgType + elemType reflect.Type + }{ + // Invalid cases. + {nil, multiArgTypeInvalid, nil}, + {Gopher{}, multiArgTypeInvalid, nil}, + {&Gopher{}, multiArgTypeInvalid, nil}, + {PropertyList{}, multiArgTypeInvalid, nil}, // This is a special case. + {PropertyMap{}, multiArgTypeInvalid, nil}, + {[]*PropertyList(nil), multiArgTypeInvalid, nil}, + {[]*PropertyMap(nil), multiArgTypeInvalid, nil}, + {[]**Gopher(nil), multiArgTypeInvalid, nil}, + {[]*interface{}(nil), multiArgTypeInvalid, nil}, + // Valid cases. + { + []PropertyList(nil), + multiArgTypePropertyLoadSaver, + reflect.TypeOf(PropertyList{}), + }, + { + []PropertyMap(nil), + multiArgTypePropertyLoadSaver, + reflect.TypeOf(PropertyMap{}), + }, + { + []StructThatImplementsPLS(nil), + multiArgTypePropertyLoadSaver, + reflect.TypeOf(StructThatImplementsPLS{}), + }, + { + []StructPtrThatImplementsPLS(nil), + multiArgTypePropertyLoadSaver, + reflect.TypeOf(StructPtrThatImplementsPLS{}), + }, + { + []Gopher(nil), + multiArgTypeStruct, + reflect.TypeOf(Gopher{}), + }, + { + []*Gopher(nil), + multiArgTypeStructPtr, + reflect.TypeOf(Gopher{}), + }, + { + []interface{}(nil), + multiArgTypeInterface, + typeOfEmptyInterface, + }, + } + for _, tc := range testCases { + mat, elemType := checkMultiArg(reflect.ValueOf(tc.v)) + if mat != tc.mat || elemType != tc.elemType { + t.Errorf("checkMultiArg(%T): got %v, %v want %v, %v", + tc.v, mat, elemType, tc.mat, tc.elemType) + } + } +} + +func TestSimpleQuery(t *testing.T) { + struct1 := Gopher{Name: "George", Height: 32} + struct2 := Gopher{Name: "Rufus"} + pList1 := PropertyList{ + { + Name: "Height", + Value: int64(32), + }, + { + Name: "Name", + Value: "George", + }, + } + pList2 := PropertyList{ + { + Name: "Name", + Value: "Rufus", + }, + } + pMap1 := PropertyMap{ + "Name": Property{ + Name: "Name", + Value: "George", + }, + "Height": Property{ + Name: "Height", + Value: int64(32), + }, + } + pMap2 := PropertyMap{ + "Name": Property{ + Name: "Name", + Value: "Rufus", + }, + } + + testCases := []struct { + dst interface{} + want interface{} + }{ + // The destination must have type *[]P, *[]S or *[]*S, for some non-interface + // type P such that *P implements PropertyLoadSaver, or for some struct type S. + {new([]Gopher), &[]Gopher{struct1, struct2}}, + {new([]*Gopher), &[]*Gopher{&struct1, &struct2}}, + {new([]PropertyList), &[]PropertyList{pList1, pList2}}, + {new([]PropertyMap), &[]PropertyMap{pMap1, pMap2}}, + + // Any other destination type is invalid. + {0, nil}, + {Gopher{}, nil}, + {PropertyList{}, nil}, + {PropertyMap{}, nil}, + {[]int{}, nil}, + {[]Gopher{}, nil}, + {[]PropertyList{}, nil}, + {new(int), nil}, + {new(Gopher), nil}, + {new(PropertyList), nil}, // This is a special case. + {new(PropertyMap), nil}, + {new([]int), nil}, + {new([]map[int]int), nil}, + {new([]map[string]Property), nil}, + {new([]map[string]interface{}), nil}, + {new([]*int), nil}, + {new([]*map[int]int), nil}, + {new([]*map[string]Property), nil}, + {new([]*map[string]interface{}), nil}, + {new([]**Gopher), nil}, + {new([]*PropertyList), nil}, + {new([]*PropertyMap), nil}, + } + for _, tc := range testCases { + nCall := 0 + client := &Client{ + client: &fakeClient{ + queryFn: func(req *pb.RunQueryRequest) (*pb.RunQueryResponse, error) { + nCall++ + return fakeRunQuery(req) + }, + }, + } + ctx := context.Background() + + var ( + expectedErr error + expectedNCall int + ) + if tc.want == nil { + expectedErr = ErrInvalidEntityType + } else { + expectedNCall = 1 + } + keys, err := client.GetAll(ctx, NewQuery("Gopher"), tc.dst) + if err != expectedErr { + t.Errorf("dst type %T: got error %v, want %v", tc.dst, err, expectedErr) + continue + } + if nCall != expectedNCall { + t.Errorf("dst type %T: Context.Call was called an incorrect number of times: got %d want %d", tc.dst, nCall, expectedNCall) + continue + } + if err != nil { + continue + } + + key1 := IDKey("Gopher", 6, nil) + expectedKeys := []*Key{ + key1, + IDKey("Gopher", 8, key1), + } + if l1, l2 := len(keys), len(expectedKeys); l1 != l2 { + t.Errorf("dst type %T: got %d keys, want %d keys", tc.dst, l1, l2) + continue + } + for i, key := range keys { + if !keysEqual(key, expectedKeys[i]) { + t.Errorf("dst type %T: got key #%d %v, want %v", tc.dst, i, key, expectedKeys[i]) + continue + } + } + + // Make sure we sort any PropertyList items (the order is not deterministic). + if pLists, ok := tc.dst.(*[]PropertyList); ok { + for _, p := range *pLists { + sort.Sort(byName(p)) + } + } + + if !reflect.DeepEqual(tc.dst, tc.want) { + t.Errorf("dst type %T: Entities\ngot %+v\nwant %+v", tc.dst, tc.dst, tc.want) + continue + } + } +} + +// keysEqual is like (*Key).Equal, but ignores the App ID. +func keysEqual(a, b *Key) bool { + for a != nil && b != nil { + if a.Kind != b.Kind || a.Name != b.Name || a.ID != b.ID { + return false + } + a, b = a.Parent, b.Parent + } + return a == b +} + +func TestQueriesAreImmutable(t *testing.T) { + // Test that deriving q2 from q1 does not modify q1. + q0 := NewQuery("foo") + q1 := NewQuery("foo") + q2 := q1.Offset(2) + if !reflect.DeepEqual(q0, q1) { + t.Errorf("q0 and q1 were not equal") + } + if reflect.DeepEqual(q1, q2) { + t.Errorf("q1 and q2 were equal") + } + + // Test that deriving from q4 twice does not conflict, even though + // q4 has a long list of order clauses. This tests that the arrays + // backed by a query's slice of orders are not shared. + f := func() *Query { + q := NewQuery("bar") + // 47 is an ugly number that is unlikely to be near a re-allocation + // point in repeated append calls. For example, it's not near a power + // of 2 or a multiple of 10. + for i := 0; i < 47; i++ { + q = q.Order(fmt.Sprintf("x%d", i)) + } + return q + } + q3 := f().Order("y") + q4 := f() + q5 := q4.Order("y") + q6 := q4.Order("z") + if !reflect.DeepEqual(q3, q5) { + t.Errorf("q3 and q5 were not equal") + } + if reflect.DeepEqual(q5, q6) { + t.Errorf("q5 and q6 were equal") + } +} + +func TestFilterParser(t *testing.T) { + testCases := []struct { + filterStr string + wantOK bool + wantFieldName string + wantOp operator + }{ + // Supported ops. + {"x<", true, "x", lessThan}, + {"x <", true, "x", lessThan}, + {"x <", true, "x", lessThan}, + {" x < ", true, "x", lessThan}, + {"x <=", true, "x", lessEq}, + {"x =", true, "x", equal}, + {"x >=", true, "x", greaterEq}, + {"x >", true, "x", greaterThan}, + {"in >", true, "in", greaterThan}, + {"in>", true, "in", greaterThan}, + // Valid but (currently) unsupported ops. + {"x!=", false, "", 0}, + {"x !=", false, "", 0}, + {" x != ", false, "", 0}, + {"x IN", false, "", 0}, + {"x in", false, "", 0}, + // Invalid ops. + {"x EQ", false, "", 0}, + {"x lt", false, "", 0}, + {"x <>", false, "", 0}, + {"x >>", false, "", 0}, + {"x ==", false, "", 0}, + {"x =<", false, "", 0}, + {"x =>", false, "", 0}, + {"x !", false, "", 0}, + {"x ", false, "", 0}, + {"x", false, "", 0}, + // Quoted and interesting field names. + {"x > y =", true, "x > y", equal}, + {"` x ` =", true, " x ", equal}, + {`" x " =`, true, " x ", equal}, + {`" \"x " =`, true, ` "x `, equal}, + {`" x =`, false, "", 0}, + {`" x ="`, false, "", 0}, + {"` x \" =", false, "", 0}, + } + for _, tc := range testCases { + q := NewQuery("foo").Filter(tc.filterStr, 42) + if ok := q.err == nil; ok != tc.wantOK { + t.Errorf("%q: ok=%t, want %t", tc.filterStr, ok, tc.wantOK) + continue + } + if !tc.wantOK { + continue + } + if len(q.filter) != 1 { + t.Errorf("%q: len=%d, want %d", tc.filterStr, len(q.filter), 1) + continue + } + got, want := q.filter[0], filter{tc.wantFieldName, tc.wantOp, 42} + if got != want { + t.Errorf("%q: got %v, want %v", tc.filterStr, got, want) + continue + } + } +} + +func TestNamespaceQuery(t *testing.T) { + gotNamespace := make(chan string, 1) + ctx := context.Background() + client := &Client{ + client: &fakeClient{ + queryFn: func(req *pb.RunQueryRequest) (*pb.RunQueryResponse, error) { + if part := req.PartitionId; part != nil { + gotNamespace <- part.NamespaceId + } else { + gotNamespace <- "" + } + return nil, errors.New("not implemented") + }, + }, + } + + var gs []Gopher + + client.GetAll(ctx, NewQuery("gopher"), &gs) + if got, want := <-gotNamespace, ""; got != want { + t.Errorf("GetAll: got namespace %q, want %q", got, want) + } + client.Count(ctx, NewQuery("gopher")) + if got, want := <-gotNamespace, ""; got != want { + t.Errorf("Count: got namespace %q, want %q", got, want) + } + + const ns = "not_default" + client.GetAll(ctx, NewQuery("gopher").Namespace(ns), &gs) + if got, want := <-gotNamespace, ns; got != want { + t.Errorf("GetAll: got namespace %q, want %q", got, want) + } + client.Count(ctx, NewQuery("gopher").Namespace(ns)) + if got, want := <-gotNamespace, ns; got != want { + t.Errorf("Count: got namespace %q, want %q", got, want) + } +} + +func TestReadOptions(t *testing.T) { + tid := []byte{1} + for _, test := range []struct { + q *Query + want *pb.ReadOptions + }{ + { + q: NewQuery(""), + want: nil, + }, + { + q: NewQuery("").Transaction(nil), + want: nil, + }, + { + q: NewQuery("").Transaction(&Transaction{id: tid}), + want: &pb.ReadOptions{ + ConsistencyType: &pb.ReadOptions_Transaction{ + Transaction: tid, + }, + }, + }, + { + q: NewQuery("").EventualConsistency(), + want: &pb.ReadOptions{ + ConsistencyType: &pb.ReadOptions_ReadConsistency_{ + ReadConsistency: pb.ReadOptions_EVENTUAL, + }, + }, + }, + } { + req := &pb.RunQueryRequest{} + if err := test.q.toProto(req); err != nil { + t.Fatalf("%+v: got %v, want no error", test.q, err) + } + if got := req.ReadOptions; !proto.Equal(got, test.want) { + t.Errorf("%+v:\ngot %+v\nwant %+v", test.q, got, test.want) + } + } + // Test errors. + for _, q := range []*Query{ + NewQuery("").Transaction(&Transaction{id: nil}), + NewQuery("").Transaction(&Transaction{id: tid}).EventualConsistency(), + } { + req := &pb.RunQueryRequest{} + if err := q.toProto(req); err == nil { + t.Errorf("%+v: got nil, wanted error", q) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save.go new file mode 100644 index 000000000..5cc557782 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save.go @@ -0,0 +1,425 @@ +// Copyright 4 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "errors" + "fmt" + "reflect" + "time" + "unicode/utf8" + + timepb "github.com/golang/protobuf/ptypes/timestamp" + pb "google.golang.org/genproto/googleapis/datastore/v1" + llpb "google.golang.org/genproto/googleapis/type/latlng" +) + +type saveOpts struct { + noIndex bool + flatten bool + omitEmpty bool +} + +// saveEntity saves an EntityProto into a PropertyLoadSaver or struct pointer. +func saveEntity(key *Key, src interface{}) (*pb.Entity, error) { + var err error + var props []Property + if e, ok := src.(PropertyLoadSaver); ok { + props, err = e.Save() + } else { + props, err = SaveStruct(src) + } + if err != nil { + return nil, err + } + return propertiesToProto(key, props) +} + +// TODO(djd): Convert this and below to return ([]Property, error). +func saveStructProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error { + p := Property{ + Name: name, + NoIndex: opts.noIndex, + } + + if opts.omitEmpty && isEmptyValue(v) { + return nil + } + + // First check if field type implements PLS. If so, use PLS to + // save. + ok, err := plsFieldSave(props, p, name, opts, v) + if err != nil { + return err + } + if ok { + return nil + } + + switch x := v.Interface().(type) { + case *Key, time.Time, GeoPoint: + p.Value = x + default: + switch v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p.Value = v.Int() + case reflect.Bool: + p.Value = v.Bool() + case reflect.String: + p.Value = v.String() + case reflect.Float32, reflect.Float64: + p.Value = v.Float() + case reflect.Slice: + if v.Type().Elem().Kind() == reflect.Uint8 { + p.Value = v.Bytes() + } else { + return saveSliceProperty(props, name, opts, v) + } + case reflect.Ptr: + if v.Type().Elem().Kind() != reflect.Struct { + return fmt.Errorf("datastore: unsupported struct field type: %s", v.Type()) + } + if v.IsNil() { + return nil + } + v = v.Elem() + fallthrough + case reflect.Struct: + if !v.CanAddr() { + return fmt.Errorf("datastore: unsupported struct field: value is unaddressable") + } + vi := v.Addr().Interface() + + sub, err := newStructPLS(vi) + if err != nil { + return fmt.Errorf("datastore: unsupported struct field: %v", err) + } + + if opts.flatten { + return sub.save(props, opts, name+".") + } + + var subProps []Property + err = sub.save(&subProps, opts, "") + if err != nil { + return err + } + subKey, err := sub.key(v) + if err != nil { + return err + } + + p.Value = &Entity{ + Key: subKey, + Properties: subProps, + } + } + } + if p.Value == nil { + return fmt.Errorf("datastore: unsupported struct field type: %v", v.Type()) + } + *props = append(*props, p) + return nil +} + +// plsFieldSave first tries to converts v's value to a PLS, then v's addressed +// value to a PLS. If neither succeeds, plsFieldSave returns false for first return +// value. +// If v is successfully converted to a PLS, plsFieldSave will then add the +// Value to property p by way of the PLS's Save method, and append it to props. +// +// If the flatten option is present in opts, name must be prepended to each property's +// name before it is appended to props. Eg. if name were "A" and a subproperty's name +// were "B", the resultant name of the property to be appended to props would be "A.B". +func plsFieldSave(props *[]Property, p Property, name string, opts saveOpts, v reflect.Value) (ok bool, err error) { + vpls, err := plsForSave(v) + if err != nil { + return false, err + } + + if vpls == nil { + return false, nil + } + + subProps, err := vpls.Save() + if err != nil { + return true, err + } + + if opts.flatten { + for _, subp := range subProps { + subp.Name = name + "." + subp.Name + *props = append(*props, subp) + } + return true, nil + } + + p.Value = &Entity{Properties: subProps} + *props = append(*props, p) + + return true, nil +} + +// key extracts the *Key struct field from struct v based on the structCodec of s. +func (s structPLS) key(v reflect.Value) (*Key, error) { + if v.Kind() != reflect.Struct { + return nil, errors.New("datastore: cannot save key of non-struct type") + } + + keyField := s.codec.Match(keyFieldName) + + if keyField == nil { + return nil, nil + } + + f := v.FieldByIndex(keyField.Index) + k, ok := f.Interface().(*Key) + if !ok { + return nil, fmt.Errorf("datastore: %s field on struct %T is not a *datastore.Key", keyFieldName, v.Interface()) + } + + return k, nil +} + +func saveSliceProperty(props *[]Property, name string, opts saveOpts, v reflect.Value) error { + // Easy case: if the slice is empty, we're done. + if v.Len() == 0 { + return nil + } + // Work out the properties generated by the first element in the slice. This will + // usually be a single property, but will be more if this is a slice of structs. + var headProps []Property + if err := saveStructProperty(&headProps, name, opts, v.Index(0)); err != nil { + return err + } + + // Convert the first element's properties into slice properties, and + // keep track of the values in a map. + values := make(map[string][]interface{}, len(headProps)) + for _, p := range headProps { + values[p.Name] = append(make([]interface{}, 0, v.Len()), p.Value) + } + + // Find the elements for the subsequent elements. + for i := 1; i < v.Len(); i++ { + elemProps := make([]Property, 0, len(headProps)) + if err := saveStructProperty(&elemProps, name, opts, v.Index(i)); err != nil { + return err + } + for _, p := range elemProps { + v, ok := values[p.Name] + if !ok { + return fmt.Errorf("datastore: unexpected property %q in elem %d of slice", p.Name, i) + } + values[p.Name] = append(v, p.Value) + } + } + + // Convert to the final properties. + for _, p := range headProps { + p.Value = values[p.Name] + *props = append(*props, p) + } + return nil +} + +func (s structPLS) Save() ([]Property, error) { + var props []Property + if err := s.save(&props, saveOpts{}, ""); err != nil { + return nil, err + } + return props, nil +} + +func (s structPLS) save(props *[]Property, opts saveOpts, prefix string) error { + for _, f := range s.codec { + name := prefix + f.Name + v := getField(s.v, f.Index) + if !v.IsValid() || !v.CanSet() { + continue + } + + var tagOpts saveOpts + if f.ParsedTag != nil { + tagOpts = f.ParsedTag.(saveOpts) + } + + var opts1 saveOpts + opts1.noIndex = opts.noIndex || tagOpts.noIndex + opts1.flatten = opts.flatten || tagOpts.flatten + opts1.omitEmpty = tagOpts.omitEmpty // don't propagate + if err := saveStructProperty(props, name, opts1, v); err != nil { + return err + } + } + return nil +} + +// getField returns the field from v at the given index path. +// If it encounters a nil-valued field in the path, getField +// stops and returns a zero-valued reflect.Value, preventing the +// panic that would have been caused by reflect's FieldByIndex. +func getField(v reflect.Value, index []int) reflect.Value { + var zero reflect.Value + if v.Type().Kind() != reflect.Struct { + return zero + } + + for _, i := range index { + if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { + if v.IsNil() { + return zero + } + v = v.Elem() + } + v = v.Field(i) + } + return v +} + +func propertiesToProto(key *Key, props []Property) (*pb.Entity, error) { + e := &pb.Entity{ + Key: keyToProto(key), + Properties: map[string]*pb.Value{}, + } + indexedProps := 0 + for _, p := range props { + // Do not send a Key value a a field to datastore. + if p.Name == keyFieldName { + continue + } + + val, err := interfaceToProto(p.Value, p.NoIndex) + if err != nil { + return nil, fmt.Errorf("datastore: %v for a Property with Name %q", err, p.Name) + } + if !p.NoIndex { + rVal := reflect.ValueOf(p.Value) + if rVal.Kind() == reflect.Slice && rVal.Type().Elem().Kind() != reflect.Uint8 { + indexedProps += rVal.Len() + } else { + indexedProps++ + } + } + if indexedProps > maxIndexedProperties { + return nil, errors.New("datastore: too many indexed properties") + } + + if _, ok := e.Properties[p.Name]; ok { + return nil, fmt.Errorf("datastore: duplicate Property with Name %q", p.Name) + } + e.Properties[p.Name] = val + } + return e, nil +} + +func interfaceToProto(iv interface{}, noIndex bool) (*pb.Value, error) { + val := &pb.Value{ExcludeFromIndexes: noIndex} + switch v := iv.(type) { + case int: + val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)} + case int32: + val.ValueType = &pb.Value_IntegerValue{IntegerValue: int64(v)} + case int64: + val.ValueType = &pb.Value_IntegerValue{IntegerValue: v} + case bool: + val.ValueType = &pb.Value_BooleanValue{BooleanValue: v} + case string: + if len(v) > 1500 && !noIndex { + return nil, errors.New("string property too long to index") + } + if !utf8.ValidString(v) { + return nil, fmt.Errorf("string is not valid utf8: %q", v) + } + val.ValueType = &pb.Value_StringValue{StringValue: v} + case float32: + val.ValueType = &pb.Value_DoubleValue{DoubleValue: float64(v)} + case float64: + val.ValueType = &pb.Value_DoubleValue{DoubleValue: v} + case *Key: + if v == nil { + val.ValueType = &pb.Value_NullValue{} + } else { + val.ValueType = &pb.Value_KeyValue{KeyValue: keyToProto(v)} + } + case GeoPoint: + if !v.Valid() { + return nil, errors.New("invalid GeoPoint value") + } + val.ValueType = &pb.Value_GeoPointValue{GeoPointValue: &llpb.LatLng{ + Latitude: v.Lat, + Longitude: v.Lng, + }} + case time.Time: + if v.Before(minTime) || v.After(maxTime) { + return nil, errors.New("time value out of range") + } + val.ValueType = &pb.Value_TimestampValue{TimestampValue: &timepb.Timestamp{ + Seconds: v.Unix(), + Nanos: int32(v.Nanosecond()), + }} + case []byte: + if len(v) > 1500 && !noIndex { + return nil, errors.New("[]byte property too long to index") + } + val.ValueType = &pb.Value_BlobValue{BlobValue: v} + case *Entity: + e, err := propertiesToProto(v.Key, v.Properties) + if err != nil { + return nil, err + } + val.ValueType = &pb.Value_EntityValue{EntityValue: e} + case []interface{}: + arr := make([]*pb.Value, 0, len(v)) + for i, v := range v { + elem, err := interfaceToProto(v, noIndex) + if err != nil { + return nil, fmt.Errorf("%v at index %d", err, i) + } + arr = append(arr, elem) + } + val.ValueType = &pb.Value_ArrayValue{ArrayValue: &pb.ArrayValue{Values: arr}} + // ArrayValues have ExcludeFromIndexes set on the individual items, rather + // than the top-level value. + val.ExcludeFromIndexes = false + default: + if iv != nil { + return nil, fmt.Errorf("invalid Value type %t", iv) + } + val.ValueType = &pb.Value_NullValue{} + } + // TODO(jbd): Support EntityValue. + return val, nil +} + +// isEmptyValue is taken from the encoding/json package in the +// standard library. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save_test.go new file mode 100644 index 000000000..7e9d9924d --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/save_test.go @@ -0,0 +1,194 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "reflect" + "testing" + + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +func TestInterfaceToProtoNilKey(t *testing.T) { + var iv *Key + pv, err := interfaceToProto(iv, false) + if err != nil { + t.Fatalf("nil key: interfaceToProto: %v", err) + } + + _, ok := pv.ValueType.(*pb.Value_NullValue) + if !ok { + t.Errorf("nil key: type:\ngot: %T\nwant: %T", pv.ValueType, &pb.Value_NullValue{}) + } +} + +func TestSaveEntityNested(t *testing.T) { + type WithKey struct { + X string + I int + K *Key `datastore:"__key__"` + } + + type NestedWithKey struct { + Y string + N WithKey + } + + type WithoutKey struct { + X string + I int + } + + type NestedWithoutKey struct { + Y string + N WithoutKey + } + + type a struct { + S string + } + + type UnexpAnonym struct { + a + } + + testCases := []struct { + desc string + src interface{} + key *Key + want *pb.Entity + }{ + { + desc: "nested entity with key", + src: &NestedWithKey{ + Y: "yyy", + N: WithKey{ + X: "two", + I: 2, + K: testKey1a, + }, + }, + key: testKey0, + want: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}}, + "N": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Key: keyToProto(testKey1a), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + }, + { + desc: "nested entity with incomplete key", + src: &NestedWithKey{ + Y: "yyy", + N: WithKey{ + X: "two", + I: 2, + K: incompleteKey, + }, + }, + key: testKey0, + want: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}}, + "N": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Key: keyToProto(incompleteKey), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + }, + { + desc: "nested entity without key", + src: &NestedWithoutKey{ + Y: "yyy", + N: WithoutKey{ + X: "two", + I: 2, + }, + }, + key: testKey0, + want: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}}, + "N": {ValueType: &pb.Value_EntityValue{ + EntityValue: &pb.Entity{ + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}}, + }, + }, + }}, + }, + }, + }, + { + desc: "key at top level", + src: &WithKey{ + X: "three", + I: 3, + K: testKey0, + }, + key: testKey0, + want: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "X": {ValueType: &pb.Value_StringValue{StringValue: "three"}}, + "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}}, + }, + }, + }, + { + desc: "nested unexported anonymous struct field", + src: &UnexpAnonym{ + a{S: "hello"}, + }, + key: testKey0, + want: &pb.Entity{ + Key: keyToProto(testKey0), + Properties: map[string]*pb.Value{ + "S": {ValueType: &pb.Value_StringValue{StringValue: "hello"}}, + }, + }, + }, + } + + for _, tc := range testCases { + got, err := saveEntity(tc.key, tc.src) + if err != nil { + t.Errorf("saveEntity: %s: %v", tc.desc, err) + continue + } + + if !reflect.DeepEqual(tc.want, got) { + t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, got, tc.want) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time.go new file mode 100644 index 000000000..e7f6a1931 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time.go @@ -0,0 +1,36 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "math" + "time" +) + +var ( + minTime = time.Unix(int64(math.MinInt64)/1e6, (int64(math.MinInt64)%1e6)*1e3) + maxTime = time.Unix(int64(math.MaxInt64)/1e6, (int64(math.MaxInt64)%1e6)*1e3) +) + +func toUnixMicro(t time.Time) int64 { + // We cannot use t.UnixNano() / 1e3 because we want to handle times more than + // 2^63 nanoseconds (which is about 292 years) away from 1970, and those cannot + // be represented in the numerator of a single int64 divide. + return t.Unix()*1e6 + int64(t.Nanosecond()/1e3) +} + +func fromUnixMicro(t int64) time.Time { + return time.Unix(t/1e6, (t%1e6)*1e3) +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time_test.go new file mode 100644 index 000000000..5cc846c4c --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/time_test.go @@ -0,0 +1,75 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "testing" + "time" +) + +func TestUnixMicro(t *testing.T) { + // Test that all these time.Time values survive a round trip to unix micros. + testCases := []time.Time{ + {}, + time.Date(2, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(23, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(234, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1000, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1600, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1700, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1800, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC), + time.Unix(-1e6, -1000), + time.Unix(-1e6, 0), + time.Unix(-1e6, +1000), + time.Unix(-60, -1000), + time.Unix(-60, 0), + time.Unix(-60, +1000), + time.Unix(-1, -1000), + time.Unix(-1, 0), + time.Unix(-1, +1000), + time.Unix(0, -3000), + time.Unix(0, -2000), + time.Unix(0, -1000), + time.Unix(0, 0), + time.Unix(0, +1000), + time.Unix(0, +2000), + time.Unix(+60, -1000), + time.Unix(+60, 0), + time.Unix(+60, +1000), + time.Unix(+1e6, -1000), + time.Unix(+1e6, 0), + time.Unix(+1e6, +1000), + time.Date(1999, 12, 31, 23, 59, 59, 999000, time.UTC), + time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(2006, 1, 2, 15, 4, 5, 678000, time.UTC), + time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC), + time.Date(3456, 1, 1, 0, 0, 0, 0, time.UTC), + } + for _, tc := range testCases { + got := fromUnixMicro(toUnixMicro(tc)) + if !got.Equal(tc) { + t.Errorf("got %q, want %q", got, tc) + } + } + + // Test that a time.Time that isn't an integral number of microseconds + // is not perfectly reconstructed after a round trip. + t0 := time.Unix(0, 123) + t1 := fromUnixMicro(toUnixMicro(t0)) + if t1.Nanosecond()%1000 != 0 || t0.Nanosecond()%1000 == 0 { + t.Errorf("quantization to µs: got %q with %d ns, started with %d ns", t1, t1.Nanosecond(), t0.Nanosecond()) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/transaction.go b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/transaction.go new file mode 100644 index 000000000..af5c427e7 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/datastore/transaction.go @@ -0,0 +1,310 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package datastore + +import ( + "errors" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + pb "google.golang.org/genproto/googleapis/datastore/v1" +) + +// ErrConcurrentTransaction is returned when a transaction is rolled back due +// to a conflict with a concurrent transaction. +var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction") + +var errExpiredTransaction = errors.New("datastore: transaction expired") + +type transactionSettings struct { + attempts int +} + +// newTransactionSettings creates a transactionSettings with a given TransactionOption slice. +// Unconfigured options will be set to default values. +func newTransactionSettings(opts []TransactionOption) *transactionSettings { + s := &transactionSettings{attempts: 3} + for _, o := range opts { + o.apply(s) + } + return s +} + +// TransactionOption configures the way a transaction is executed. +type TransactionOption interface { + apply(*transactionSettings) +} + +// MaxAttempts returns a TransactionOption that overrides the default 3 attempt times. +func MaxAttempts(attempts int) TransactionOption { + return maxAttempts(attempts) +} + +type maxAttempts int + +func (w maxAttempts) apply(s *transactionSettings) { + if w > 0 { + s.attempts = int(w) + } +} + +// Transaction represents a set of datastore operations to be committed atomically. +// +// Operations are enqueued by calling the Put and Delete methods on Transaction +// (or their Multi-equivalents). These operations are only committed when the +// Commit method is invoked. To ensure consistency, reads must be performed by +// using Transaction's Get method or by using the Transaction method when +// building a query. +// +// A Transaction must be committed or rolled back exactly once. +type Transaction struct { + id []byte + client *Client + ctx context.Context + mutations []*pb.Mutation // The mutations to apply. + pending map[int]*PendingKey // Map from mutation index to incomplete keys pending transaction completion. +} + +// NewTransaction starts a new transaction. +func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (*Transaction, error) { + for _, o := range opts { + if _, ok := o.(maxAttempts); ok { + return nil, errors.New("datastore: NewTransaction does not accept MaxAttempts option") + } + } + req := &pb.BeginTransactionRequest{ + ProjectId: c.dataset, + } + resp, err := c.client.BeginTransaction(ctx, req) + if err != nil { + return nil, err + } + + return &Transaction{ + id: resp.Transaction, + ctx: ctx, + client: c, + mutations: nil, + pending: make(map[int]*PendingKey), + }, nil +} + +// RunInTransaction runs f in a transaction. f is invoked with a Transaction +// that f should use for all the transaction's datastore operations. +// +// f must not call Commit or Rollback on the provided Transaction. +// +// If f returns nil, RunInTransaction commits the transaction, +// returning the Commit and a nil error if it succeeds. If the commit fails due +// to a conflicting transaction, RunInTransaction retries f with a new +// Transaction. It gives up and returns ErrConcurrentTransaction after three +// failed attempts (or as configured with MaxAttempts). +// +// If f returns non-nil, then the transaction will be rolled back and +// RunInTransaction will return the same error. The function f is not retried. +// +// Note that when f returns, the transaction is not committed. Calling code +// must not assume that any of f's changes have been committed until +// RunInTransaction returns nil. +// +// Since f may be called multiple times, f should usually be idempotent – that +// is, it should have the same result when called multiple times. Note that +// Transaction.Get will append when unmarshalling slice fields, so it is not +// necessarily idempotent. +func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (*Commit, error) { + settings := newTransactionSettings(opts) + for n := 0; n < settings.attempts; n++ { + tx, err := c.NewTransaction(ctx) + if err != nil { + return nil, err + } + if err := f(tx); err != nil { + tx.Rollback() + return nil, err + } + if cmt, err := tx.Commit(); err != ErrConcurrentTransaction { + return cmt, err + } + } + return nil, ErrConcurrentTransaction +} + +// Commit applies the enqueued operations atomically. +func (t *Transaction) Commit() (*Commit, error) { + if t.id == nil { + return nil, errExpiredTransaction + } + req := &pb.CommitRequest{ + ProjectId: t.client.dataset, + TransactionSelector: &pb.CommitRequest_Transaction{Transaction: t.id}, + Mutations: t.mutations, + Mode: pb.CommitRequest_TRANSACTIONAL, + } + t.id = nil + resp, err := t.client.client.Commit(t.ctx, req) + if err != nil { + if grpc.Code(err) == codes.Aborted { + return nil, ErrConcurrentTransaction + } + return nil, err + } + + // Copy any newly minted keys into the returned keys. + commit := &Commit{} + for i, p := range t.pending { + if i >= len(resp.MutationResults) || resp.MutationResults[i].Key == nil { + return nil, errors.New("datastore: internal error: server returned the wrong mutation results") + } + key, err := protoToKey(resp.MutationResults[i].Key) + if err != nil { + return nil, errors.New("datastore: internal error: server returned an invalid key") + } + p.key = key + p.commit = commit + } + + return commit, nil +} + +// Rollback abandons a pending transaction. +func (t *Transaction) Rollback() error { + if t.id == nil { + return errExpiredTransaction + } + id := t.id + t.id = nil + _, err := t.client.client.Rollback(t.ctx, &pb.RollbackRequest{ + ProjectId: t.client.dataset, + Transaction: id, + }) + return err +} + +// Get is the transaction-specific version of the package function Get. +// All reads performed during the transaction will come from a single consistent +// snapshot. Furthermore, if the transaction is set to a serializable isolation +// level, another transaction cannot concurrently modify the data that is read +// or modified by this transaction. +func (t *Transaction) Get(key *Key, dst interface{}) error { + opts := &pb.ReadOptions{ + ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id}, + } + err := t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts) + if me, ok := err.(MultiError); ok { + return me[0] + } + return err +} + +// GetMulti is a batch version of Get. +func (t *Transaction) GetMulti(keys []*Key, dst interface{}) error { + if t.id == nil { + return errExpiredTransaction + } + opts := &pb.ReadOptions{ + ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id}, + } + return t.client.get(t.ctx, keys, dst, opts) +} + +// Put is the transaction-specific version of the package function Put. +// +// Put returns a PendingKey which can be resolved into a Key using the +// return value from a successful Commit. If key is an incomplete key, the +// returned pending key will resolve to a unique key generated by the +// datastore. +func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) { + h, err := t.PutMulti([]*Key{key}, []interface{}{src}) + if err != nil { + if me, ok := err.(MultiError); ok { + return nil, me[0] + } + return nil, err + } + return h[0], nil +} + +// PutMulti is a batch version of Put. One PendingKey is returned for each +// element of src in the same order. +func (t *Transaction) PutMulti(keys []*Key, src interface{}) ([]*PendingKey, error) { + if t.id == nil { + return nil, errExpiredTransaction + } + mutations, err := putMutations(keys, src) + if err != nil { + return nil, err + } + origin := len(t.mutations) + t.mutations = append(t.mutations, mutations...) + + // Prepare the returned handles, pre-populating where possible. + ret := make([]*PendingKey, len(keys)) + for i, key := range keys { + p := &PendingKey{} + if key.Incomplete() { + // This key will be in the final commit result. + t.pending[origin+i] = p + } else { + p.key = key + } + ret[i] = p + } + + return ret, nil +} + +// Delete is the transaction-specific version of the package function Delete. +// Delete enqueues the deletion of the entity for the given key, to be +// committed atomically upon calling Commit. +func (t *Transaction) Delete(key *Key) error { + err := t.DeleteMulti([]*Key{key}) + if me, ok := err.(MultiError); ok { + return me[0] + } + return err +} + +// DeleteMulti is a batch version of Delete. +func (t *Transaction) DeleteMulti(keys []*Key) error { + if t.id == nil { + return errExpiredTransaction + } + mutations, err := deleteMutations(keys) + if err != nil { + return err + } + t.mutations = append(t.mutations, mutations...) + return nil +} + +// Commit represents the result of a committed transaction. +type Commit struct{} + +// Key resolves a pending key handle into a final key. +func (c *Commit) Key(p *PendingKey) *Key { + if c != p.commit { + panic("PendingKey was not created by corresponding transaction") + } + return p.key +} + +// PendingKey represents the key for newly-inserted entity. It can be +// resolved into a Key by calling the Key method of Commit. +type PendingKey struct { + key *Key + commit *Commit +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache.go new file mode 100644 index 000000000..2bea8a150 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache.go @@ -0,0 +1,58 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package atomiccache provides a map-based cache that supports very fast +// reads. +package atomiccache + +import ( + "sync" + "sync/atomic" +) + +type mapType map[interface{}]interface{} + +// Cache is a map-based cache that supports fast reads via use of atomics. +// Writes are slow, requiring a copy of the entire cache. +// The zero Cache is an empty cache, ready for use. +type Cache struct { + val atomic.Value // mapType + mu sync.Mutex // used only by writers +} + +// Get returns the value of the cache at key. If there is no value, +// getter is called to provide one, and the cache is updated. +// The getter function may be called concurrently. It should be pure, +// returning the same value for every call. +func (c *Cache) Get(key interface{}, getter func() interface{}) interface{} { + mp, _ := c.val.Load().(mapType) + if v, ok := mp[key]; ok { + return v + } + + // Compute value without lock. + // Might duplicate effort but won't hold other computations back. + newV := getter() + + c.mu.Lock() + mp, _ = c.val.Load().(mapType) + newM := make(mapType, len(mp)+1) + for k, v := range mp { + newM[k] = v + } + newM[key] = newV + c.val.Store(newM) + c.mu.Unlock() + return newV +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache_test.go new file mode 100644 index 000000000..33105b343 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/atomiccache/atomiccache_test.go @@ -0,0 +1,46 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atomiccache + +import ( + "fmt" + "testing" +) + +func TestGet(t *testing.T) { + var c Cache + called := false + get := func(k interface{}) interface{} { + return c.Get(k, func() interface{} { + called = true + return fmt.Sprintf("v%d", k) + }) + } + got := get(1) + if want := "v1"; got != want { + t.Errorf("got %v, want %v", got, want) + } + if !called { + t.Error("getter not called, expected a call") + } + called = false + got = get(1) + if want := "v1"; got != want { + t.Errorf("got %v, want %v", got, want) + } + if called { + t.Error("getter unexpectedly called") + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields.go new file mode 100644 index 000000000..4f5516eae --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields.go @@ -0,0 +1,444 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package fields provides a view of the fields of a struct that follows the Go +// rules, amended to consider tags and case insensitivity. +// +// Usage +// +// First define a function that interprets tags: +// +// func parseTag(st reflect.StructTag) (name string, keep bool, other interface{}, err error) { ... } +// +// The function's return values describe whether to ignore the field +// completely or provide an alternate name, as well as other data from the +// parse that is stored to avoid re-parsing. +// +// Then define a function to validate the type: +// +// func validate(t reflect.Type) error { ... } +// +// Then, if necessary, define a function to specify leaf types - types +// which should be considered one field and not be recursed into: +// +// func isLeafType(t reflect.Type) bool { ... } +// +// eg: +// +// func isLeafType(t reflect.Type) bool { +// return t == reflect.TypeOf(time.Time{}) +// } +// +// Next, construct a Cache, passing your functions. As its name suggests, a +// Cache remembers validation and field information for a type, so subsequent +// calls with the same type are very fast. +// +// cache := fields.NewCache(parseTag, validate, isLeafType) +// +// To get the fields of a struct type as determined by the above rules, call +// the Fields method: +// +// fields, err := cache.Fields(reflect.TypeOf(MyStruct{})) +// +// The return value can be treated as a slice of Fields. +// +// Given a string, such as a key or column name obtained during unmarshalling, +// call Match on the list of fields to find a field whose name is the best +// match: +// +// field := fields.Match(name) +// +// Match looks for an exact match first, then falls back to a case-insensitive +// comparison. +package fields + +import ( + "bytes" + "reflect" + "sort" + + "cloud.google.com/go/internal/atomiccache" +) + +// A Field records information about a struct field. +type Field struct { + Name string // effective field name + NameFromTag bool // did Name come from a tag? + Type reflect.Type // field type + Index []int // index sequence, for reflect.Value.FieldByIndex + ParsedTag interface{} // third return value of the parseTag function + + nameBytes []byte + equalFold func(s, t []byte) bool +} + +type ParseTagFunc func(reflect.StructTag) (name string, keep bool, other interface{}, err error) + +type ValidateFunc func(reflect.Type) error + +type LeafTypesFunc func(reflect.Type) bool + +// A Cache records information about the fields of struct types. +// +// A Cache is safe for use by multiple goroutines. +type Cache struct { + parseTag ParseTagFunc + validate ValidateFunc + leafTypes LeafTypesFunc + cache atomiccache.Cache // from reflect.Type to cacheValue +} + +// NewCache constructs a Cache. +// +// Its first argument should be a function that accepts +// a struct tag and returns four values: an alternative name for the field +// extracted from the tag, a boolean saying whether to keep the field or ignore +// it, additional data that is stored with the field information to avoid +// having to parse the tag again, and an error. +// +// Its second argument should be a function that accepts a reflect.Type and +// returns an error if the struct type is invalid in any way. For example, it +// may check that all of the struct field tags are valid, or that all fields +// are of an appropriate type. +func NewCache(parseTag ParseTagFunc, validate ValidateFunc, leafTypes LeafTypesFunc) *Cache { + if parseTag == nil { + parseTag = func(reflect.StructTag) (string, bool, interface{}, error) { + return "", true, nil, nil + } + } + if validate == nil { + validate = func(reflect.Type) error { + return nil + } + } + if leafTypes == nil { + leafTypes = func(reflect.Type) bool { + return false + } + } + + return &Cache{ + parseTag: parseTag, + validate: validate, + leafTypes: leafTypes, + } +} + +// A fieldScan represents an item on the fieldByNameFunc scan work list. +type fieldScan struct { + typ reflect.Type + index []int +} + +// Fields returns all the exported fields of t, which must be a struct type. It +// follows the standard Go rules for embedded fields, modified by the presence +// of tags. The result is sorted lexicographically by index. +// +// These rules apply in the absence of tags: +// Anonymous struct fields are treated as if their inner exported fields were +// fields in the outer struct (embedding). The result includes all fields that +// aren't shadowed by fields at higher level of embedding. If more than one +// field with the same name exists at the same level of embedding, it is +// excluded. An anonymous field that is not of struct type is treated as having +// its type as its name. +// +// Tags modify these rules as follows: +// A field's tag is used as its name. +// An anonymous struct field with a name given in its tag is treated as +// a field having that name, rather than an embedded struct (the struct's +// fields will not be returned). +// If more than one field with the same name exists at the same level of embedding, +// but exactly one of them is tagged, then the tagged field is reported and the others +// are ignored. +func (c *Cache) Fields(t reflect.Type) (List, error) { + if t.Kind() != reflect.Struct { + panic("fields: Fields of non-struct type") + } + return c.cachedTypeFields(t) +} + +// A List is a list of Fields. +type List []Field + +// Match returns the field in the list whose name best matches the supplied +// name, nor nil if no field does. If there is a field with the exact name, it +// is returned. Otherwise the first field (sorted by index) whose name matches +// case-insensitively is returned. +func (l List) Match(name string) *Field { + return l.MatchBytes([]byte(name)) +} + +// MatchBytes is identical to Match, except that the argument is a byte slice. +func (l List) MatchBytes(name []byte) *Field { + var f *Field + for i := range l { + ff := &l[i] + if bytes.Equal(ff.nameBytes, name) { + return ff + } + if f == nil && ff.equalFold(ff.nameBytes, name) { + f = ff + } + } + return f +} + +type cacheValue struct { + fields List + err error +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +// This code has been copied and modified from +// https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/encode.go. +func (c *Cache) cachedTypeFields(t reflect.Type) (List, error) { + cv := c.cache.Get(t, func() interface{} { + if err := c.validate(t); err != nil { + return cacheValue{nil, err} + } + f, err := c.typeFields(t) + return cacheValue{List(f), err} + }).(cacheValue) + return cv.fields, cv.err +} + +func (c *Cache) typeFields(t reflect.Type) ([]Field, error) { + fields, err := c.listFields(t) + if err != nil { + return nil, err + } + sort.Sort(byName(fields)) + // Delete all fields that are hidden by the Go rules for embedded fields. + + // The fields are sorted in primary order of name, secondary order of field + // index length. So the first field with a given name is the dominant one. + var out []Field + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.Name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.Name != name { + break + } + } + // Find the dominant field, if any, out of all fields that have the same name. + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + sort.Sort(byIndex(out)) + return out, nil +} + +func (c *Cache) listFields(t reflect.Type) ([]Field, error) { + // This uses the same condition that the Go language does: there must be a unique instance + // of the match at a given depth level. If there are multiple instances of a match at the + // same depth, they annihilate each other and inhibit any possible match at a lower level. + // The algorithm is breadth first search, one depth level at a time. + + // The current and next slices are work queues: + // current lists the fields to visit on this depth level, + // and next lists the fields on the next lower level. + current := []fieldScan{} + next := []fieldScan{{typ: t}} + + // nextCount records the number of times an embedded type has been + // encountered and considered for queueing in the 'next' slice. + // We only queue the first one, but we increment the count on each. + // If a struct type T can be reached more than once at a given depth level, + // then it annihilates itself and need not be considered at all when we + // process that next depth level. + var nextCount map[reflect.Type]int + + // visited records the structs that have been considered already. + // Embedded pointer fields can create cycles in the graph of + // reachable embedded types; visited avoids following those cycles. + // It also avoids duplicated effort: if we didn't find the field in an + // embedded type T at level 2, we won't find it in one at level 4 either. + visited := map[reflect.Type]bool{} + + var fields []Field // Fields found. + + for len(next) > 0 { + current, next = next, current[:0] + count := nextCount + nextCount = nil + + // Process all the fields at this depth, now listed in 'current'. + // The loop queues embedded fields found in 'next', for processing during the next + // iteration. The multiplicity of the 'current' field counts is recorded + // in 'count'; the multiplicity of the 'next' field counts is recorded in 'nextCount'. + for _, scan := range current { + t := scan.typ + if visited[t] { + // We've looked through this type before, at a higher level. + // That higher level would shadow the lower level we're now at, + // so this one can't be useful to us. Ignore it. + continue + } + visited[t] = true + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + + exported := (f.PkgPath == "") + + // If a named field is unexported, ignore it. An anonymous + // unexported field is processed, because it may contain + // exported fields, which are visible. + if !exported && !f.Anonymous { + continue + } + + // Examine the tag. + tagName, keep, other, err := c.parseTag(f.Tag) + if err != nil { + return nil, err + } + if !keep { + continue + } + if c.leafTypes(f.Type) { + fields = append(fields, newField(f, tagName, other, scan.index, i)) + continue + } + + var ntyp reflect.Type + if f.Anonymous { + // Anonymous field of type T or *T. + ntyp = f.Type + if ntyp.Kind() == reflect.Ptr { + ntyp = ntyp.Elem() + } + } + + // Record fields with a tag name, non-anonymous fields, or + // anonymous non-struct fields. + if tagName != "" || ntyp == nil || ntyp.Kind() != reflect.Struct { + if !exported { + continue + } + fields = append(fields, newField(f, tagName, other, scan.index, i)) + if count[t] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Queue embedded struct fields for processing with next level, + // but only if the embedded types haven't already been queued. + if nextCount[ntyp] > 0 { + nextCount[ntyp] = 2 // exact multiple doesn't matter + continue + } + if nextCount == nil { + nextCount = map[reflect.Type]int{} + } + nextCount[ntyp] = 1 + if count[t] > 1 { + nextCount[ntyp] = 2 // exact multiple doesn't matter + } + var index []int + index = append(index, scan.index...) + index = append(index, i) + next = append(next, fieldScan{ntyp, index}) + } + } + } + return fields, nil +} + +func newField(f reflect.StructField, tagName string, other interface{}, index []int, i int) Field { + name := tagName + if name == "" { + name = f.Name + } + sf := Field{ + Name: name, + NameFromTag: tagName != "", + Type: f.Type, + ParsedTag: other, + nameBytes: []byte(name), + } + sf.equalFold = foldFunc(sf.nameBytes) + sf.Index = append(sf.Index, index...) + sf.Index = append(sf.Index, i) + return sf +} + +// byName sorts fields using the following criteria, in order: +// 1. name +// 2. embedding depth +// 3. tag presence (preferring a tagged field) +// 4. index sequence. +type byName []Field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].Name != x[j].Name { + return x[i].Name < x[j].Name + } + if len(x[i].Index) != len(x[j].Index) { + return len(x[i].Index) < len(x[j].Index) + } + if x[i].NameFromTag != x[j].NameFromTag { + return x[i].NameFromTag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []Field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + xi := x[i].Index + xj := x[j].Index + ln := len(xi) + if l := len(xj); l < ln { + ln = l + } + for k := 0; k < ln; k++ { + if xi[k] != xj[k] { + return xi[k] < xj[k] + } + } + return len(xi) < len(xj) +} + +// dominantField looks through the fields, all of which are known to have the +// same name, to find the single field that dominates the others using Go's +// embedding rules, modified by the presence of tags. If there are multiple +// top-level fields, the boolean will be false: This condition is an error in +// Go and we skip all the fields. +func dominantField(fs []Field) (Field, bool) { + // The fields are sorted in increasing index-length order, then by presence of tag. + // That means that the first field is the dominant one. We need only check + // for error cases: two fields at top level, either both tagged or neither tagged. + if len(fs) > 1 && len(fs[0].Index) == len(fs[1].Index) && fs[0].NameFromTag == fs[1].NameFromTag { + return Field{}, false + } + return fs[0], true +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields_test.go new file mode 100644 index 000000000..904d8b855 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fields_test.go @@ -0,0 +1,561 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fields + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" + "testing" + "time" +) + +type embed1 struct { + Em1 int + Dup int // annihilates with embed2.Dup + Shadow int + embed3 +} + +type embed2 struct { + Dup int + embed3 + embed4 +} + +type embed3 struct { + Em3 int // annihilated because embed3 is in both embed1 and embed2 + embed5 +} + +type embed4 struct { + Em4 int + Dup int // annihilation of Dup in embed1, embed2 hides this Dup + *embed1 // ignored because it occurs at a higher level +} + +type embed5 struct { + x int +} + +type Anonymous int + +type S1 struct { + Exported int + unexported int + Shadow int // shadows S1.Shadow + embed1 + *embed2 + Anonymous +} + +type Time struct { + time.Time +} + +var intType = reflect.TypeOf(int(0)) + +func field(name string, tval interface{}, index ...int) *Field { + return &Field{ + Name: name, + Type: reflect.TypeOf(tval), + Index: index, + } +} + +func tfield(name string, tval interface{}, index ...int) *Field { + return &Field{ + Name: name, + Type: reflect.TypeOf(tval), + Index: index, + NameFromTag: true, + } +} + +func TestFieldsNoTags(t *testing.T) { + c := NewCache(nil, nil, nil) + got, err := c.Fields(reflect.TypeOf(S1{})) + if err != nil { + t.Fatal(err) + } + want := []*Field{ + field("Exported", int(0), 0), + field("Shadow", int(0), 2), + field("Em1", int(0), 3, 0), + field("Em4", int(0), 4, 2, 0), + field("Anonymous", Anonymous(0), 5), + } + if msg, ok := compareFields(got, want); !ok { + t.Error(msg) + } +} + +func TestAgainstJSONEncodingNoTags(t *testing.T) { + // Demonstrates that this package produces the same set of fields as encoding/json. + s1 := S1{ + Exported: 1, + unexported: 2, + Shadow: 3, + embed1: embed1{ + Em1: 4, + Dup: 5, + Shadow: 6, + embed3: embed3{ + Em3: 7, + embed5: embed5{x: 8}, + }, + }, + embed2: &embed2{ + Dup: 9, + embed3: embed3{ + Em3: 10, + embed5: embed5{x: 11}, + }, + embed4: embed4{ + Em4: 12, + Dup: 13, + embed1: &embed1{Em1: 14}, + }, + }, + Anonymous: Anonymous(15), + } + var want S1 + jsonRoundTrip(t, s1, &want) + var got S1 + got.embed2 = &embed2{} // need this because reflection won't create it + fields, err := NewCache(nil, nil, nil).Fields(reflect.TypeOf(got)) + if err != nil { + t.Fatal(err) + } + setFields(fields, &got, s1) + if !reflect.DeepEqual(got, want) { + t.Errorf("got\n%+v\nwant\n%+v", got, want) + } +} + +// Tests use of LeafTypes parameter to NewCache +func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) { + timeLeafFn := func(t reflect.Type) bool { + return t == reflect.TypeOf(time.Time{}) + } + // Demonstrates that this package can produce the same set of + // fields as encoding/json for a struct with an embedded time.Time. + now := time.Now().UTC() + myt := Time{ + now, + } + var want Time + jsonRoundTrip(t, myt, &want) + var got Time + fields, err := NewCache(nil, nil, timeLeafFn).Fields(reflect.TypeOf(got)) + if err != nil { + t.Fatal(err) + } + setFields(fields, &got, myt) + if !reflect.DeepEqual(got, want) { + t.Errorf("got\n%+v\nwant\n%+v", got, want) + } +} + +type S2 struct { + NoTag int + XXX int `json:"tag"` // tag name takes precedence + Anonymous `json:"anon"` // anonymous non-structs also get their name from the tag + unexported int `json:"tag"` + Embed `json:"em"` // embedded structs with tags become fields + Tag int + YYY int `json:"Tag"` // tag takes precedence over untagged field of the same name + Empty int `json:""` // empty tag is noop + tEmbed1 + tEmbed2 +} + +type Embed struct { + Em int +} + +type tEmbed1 struct { + Dup int + X int `json:"Dup2"` +} + +type tEmbed2 struct { + Y int `json:"Dup"` // takes precedence over tEmbed1.Dup because it is tagged + Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored +} + +func jsonTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) { + s := t.Get("json") + parts := strings.Split(s, ",") + if parts[0] == "-" { + return "", false, nil, nil + } + if len(parts) > 1 { + other = parts[1:] + } + return parts[0], true, other, nil +} + +func validateFunc(t reflect.Type) (err error) { + if t.Kind() != reflect.Struct { + return errors.New("non-struct type used") + } + + for i := 0; i < t.NumField(); i++ { + if t.Field(i).Type.Kind() == reflect.Slice { + return fmt.Errorf("slice field found at field %s on struct %s", t.Field(i).Name, t.Name()) + } + } + + return nil +} + +func TestFieldsWithTags(t *testing.T) { + got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{})) + if err != nil { + t.Fatal(err) + } + want := []*Field{ + field("NoTag", int(0), 0), + tfield("tag", int(0), 1), + tfield("anon", Anonymous(0), 2), + tfield("em", Embed{}, 4), + tfield("Tag", int(0), 6), + field("Empty", int(0), 7), + tfield("Dup", int(0), 8, 0), + } + if msg, ok := compareFields(got, want); !ok { + t.Error(msg) + } +} + +func TestAgainstJSONEncodingWithTags(t *testing.T) { + // Demonstrates that this package produces the same set of fields as encoding/json. + s2 := S2{ + NoTag: 1, + XXX: 2, + Anonymous: 3, + Embed: Embed{ + Em: 4, + }, + tEmbed1: tEmbed1{ + Dup: 5, + X: 6, + }, + tEmbed2: tEmbed2{ + Y: 7, + Z: 8, + }, + } + var want S2 + jsonRoundTrip(t, s2, &want) + var got S2 + fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(got)) + if err != nil { + t.Fatal(err) + } + setFields(fields, &got, s2) + if !reflect.DeepEqual(got, want) { + t.Errorf("got\n%+v\nwant\n%+v", got, want) + } +} + +func TestUnexportedAnonymousNonStruct(t *testing.T) { + // An unexported anonymous non-struct field should not be recorded. + // This is currently a bug in encoding/json. + // https://github.com/golang/go/issues/18009 + type ( + u int + v int + S struct { + u + v `json:"x"` + int + } + ) + + got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) + if err != nil { + t.Fatal(err) + } + if len(got) != 0 { + t.Errorf("got %d fields, want 0", len(got)) + } +} + +func TestUnexportedAnonymousStruct(t *testing.T) { + // An unexported anonymous struct with a tag is ignored. + // This is currently a bug in encoding/json. + // https://github.com/golang/go/issues/18009 + type ( + s1 struct{ X int } + S2 struct { + s1 `json:"Y"` + } + ) + got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{})) + if err != nil { + t.Fatal(err) + } + if len(got) != 0 { + t.Errorf("got %d fields, want 0", len(got)) + } +} + +func TestDominantField(t *testing.T) { + // With fields sorted by index length and then by tag presence, + // the dominant field is always the first. Make sure all error + // cases are caught. + for _, test := range []struct { + fields []Field + wantOK bool + }{ + // A single field is OK. + {[]Field{{Index: []int{0}}}, true}, + {[]Field{{Index: []int{0}, NameFromTag: true}}, true}, + // A single field at top level is OK. + {[]Field{{Index: []int{0}}, {Index: []int{1, 0}}}, true}, + {[]Field{{Index: []int{0}}, {Index: []int{1, 0}, NameFromTag: true}}, true}, + {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1, 0}, NameFromTag: true}}, true}, + // A single tagged field is OK. + {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true}, + // Two untagged fields at the same level is an error. + {[]Field{{Index: []int{0}}, {Index: []int{1}}}, false}, + // Two tagged fields at the same level is an error. + {[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}, NameFromTag: true}}, false}, + } { + _, gotOK := dominantField(test.fields) + if gotOK != test.wantOK { + t.Errorf("%v: got %t, want %t", test.fields, gotOK, test.wantOK) + } + } +} + +func TestIgnore(t *testing.T) { + type S struct { + X int `json:"-"` + } + got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) + if err != nil { + t.Fatal(err) + } + if len(got) != 0 { + t.Errorf("got %d fields, want 0", len(got)) + } +} + +func TestParsedTag(t *testing.T) { + type S struct { + X int `json:"name,omitempty"` + } + got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{})) + if err != nil { + t.Fatal(err) + } + want := []*Field{ + {Name: "name", NameFromTag: true, Type: intType, + Index: []int{0}, ParsedTag: []string{"omitempty"}}, + } + if msg, ok := compareFields(got, want); !ok { + t.Error(msg) + } +} + +func TestValidateFunc(t *testing.T) { + type MyInvalidStruct struct { + A string + B []int + } + + _, err := NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyInvalidStruct{})) + if err == nil { + t.Fatal("expected error, got nil") + } + + type MyValidStruct struct { + A string + B int + } + _, err = NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyValidStruct{})) + if err != nil { + t.Fatalf("expected nil, got error: %s\n", err) + } +} + +func compareFields(got []Field, want []*Field) (msg string, ok bool) { + if len(got) != len(want) { + return fmt.Sprintf("got %d fields, want %d", len(got), len(want)), false + } + for i, g := range got { + w := *want[i] + if !fieldsEqual(&g, &w) { + return fmt.Sprintf("got %+v, want %+v", g, w), false + } + } + return "", true +} + +// Need this because Field contains a function, which cannot be compared even +// by reflect.DeepEqual. +func fieldsEqual(f1, f2 *Field) bool { + if f1 == nil || f2 == nil { + return f1 == f2 + } + return f1.Name == f2.Name && + f1.NameFromTag == f2.NameFromTag && + f1.Type == f2.Type && + reflect.DeepEqual(f1.ParsedTag, f2.ParsedTag) +} + +// Set the fields of dst from those of src. +// dst must be a pointer to a struct value. +// src must be a struct value. +func setFields(fields []Field, dst, src interface{}) { + vsrc := reflect.ValueOf(src) + vdst := reflect.ValueOf(dst).Elem() + for _, f := range fields { + fdst := vdst.FieldByIndex(f.Index) + fsrc := vsrc.FieldByIndex(f.Index) + fdst.Set(fsrc) + } +} + +func jsonRoundTrip(t *testing.T, in, out interface{}) { + bytes, err := json.Marshal(in) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(bytes, out); err != nil { + t.Fatal(err) + } +} + +type S3 struct { + S4 + Abc int + AbC int + Tag int + X int `json:"Tag"` + unexported int +} + +type S4 struct { + ABc int + Y int `json:"Abc"` // ignored because of top-level Abc +} + +func TestMatchingField(t *testing.T) { + fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{})) + if err != nil { + t.Fatal(err) + } + for _, test := range []struct { + name string + want *Field + }{ + // Exact match wins. + {"Abc", field("Abc", int(0), 1)}, + {"AbC", field("AbC", int(0), 2)}, + {"ABc", field("ABc", int(0), 0, 0)}, + // If there are multiple matches but no exact match or tag, + // the first field wins, lexicographically by index. + // Here, "ABc" is at a deeper embedding level, but since S4 appears + // first in S3, its index precedes the other fields of S3. + {"abc", field("ABc", int(0), 0, 0)}, + // Tag name takes precedence over untagged field of the same name. + {"Tag", tfield("Tag", int(0), 4)}, + // Unexported fields disappear. + {"unexported", nil}, + // Untagged embedded structs disappear. + {"S4", nil}, + } { + if got := fields.Match(test.name); !fieldsEqual(got, test.want) { + t.Errorf("match %q:\ngot %+v\nwant %+v", test.name, got, test.want) + } + } +} + +func TestAgainstJSONMatchingField(t *testing.T) { + s3 := S3{ + S4: S4{ABc: 1, Y: 2}, + Abc: 3, + AbC: 4, + Tag: 5, + X: 6, + unexported: 7, + } + var want S3 + jsonRoundTrip(t, s3, &want) + v := reflect.ValueOf(want) + fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{})) + if err != nil { + t.Fatal(err) + } + for _, test := range []struct { + name string + got int + }{ + {"Abc", 3}, + {"AbC", 4}, + {"ABc", 1}, + {"abc", 1}, + {"Tag", 6}, + } { + f := fields.Match(test.name) + if f == nil { + t.Fatalf("%s: no match", test.name) + } + w := v.FieldByIndex(f.Index).Interface() + if test.got != w { + t.Errorf("%s: got %d, want %d", test.name, test.got, w) + } + } +} + +func TestTagErrors(t *testing.T) { + called := false + c := NewCache(func(t reflect.StructTag) (string, bool, interface{}, error) { + called = true + s := t.Get("f") + if s == "bad" { + return "", false, nil, errors.New("error") + } + return s, true, nil, nil + }, nil, nil) + + type T struct { + X int `f:"ok"` + Y int `f:"bad"` + } + + _, err := c.Fields(reflect.TypeOf(T{})) + if !called { + t.Fatal("tag parser not called") + } + if err == nil { + t.Error("want error, got nil") + } + // Second time, we should cache the error. + called = false + _, err = c.Fields(reflect.TypeOf(T{})) + if called { + t.Fatal("tag parser called on second time") + } + if err == nil { + t.Error("want error, got nil") + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold.go new file mode 100644 index 000000000..10a68189c --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold.go @@ -0,0 +1,156 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fields + +// This file was copied from https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/fold.go. +// Only the license and package were changed. + +import ( + "bytes" + "unicode/utf8" +) + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// * S maps to s and to U+017F 'Å¿' Latin small letter long s +// * k maps to K and to U+212A 'K' Kelvin sign +// See https://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + if len(t) > 0 { + return false + } + return true +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold_test.go new file mode 100644 index 000000000..eadded18d --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/fields/fold_test.go @@ -0,0 +1,129 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fields + +// This file was copied from https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/fold_test.go. +// Only the license and package were changed. + +import ( + "bytes" + "strings" + "testing" + "unicode/utf8" +) + +var foldTests = []struct { + fn func(s, t []byte) bool + s, t string + want bool +}{ + {equalFoldRight, "", "", true}, + {equalFoldRight, "a", "a", true}, + {equalFoldRight, "", "a", false}, + {equalFoldRight, "a", "", false}, + {equalFoldRight, "a", "A", true}, + {equalFoldRight, "AB", "ab", true}, + {equalFoldRight, "AB", "ac", false}, + {equalFoldRight, "sbkKc", "Å¿bKKc", true}, + {equalFoldRight, "SbKkc", "Å¿bKKc", true}, + {equalFoldRight, "SbKkc", "Å¿bKK", false}, + {equalFoldRight, "e", "é", false}, + {equalFoldRight, "s", "S", true}, + + {simpleLetterEqualFold, "", "", true}, + {simpleLetterEqualFold, "abc", "abc", true}, + {simpleLetterEqualFold, "abc", "ABC", true}, + {simpleLetterEqualFold, "abc", "ABCD", false}, + {simpleLetterEqualFold, "abc", "xxx", false}, + + {asciiEqualFold, "a_B", "A_b", true}, + {asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent +} + +func TestFold(t *testing.T) { + for i, tt := range foldTests { + if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want { + t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want) + } + truth := strings.EqualFold(tt.s, tt.t) + if truth != tt.want { + t.Errorf("strings.EqualFold doesn't agree with case %d", i) + } + } +} + +func TestFoldAgainstUnicode(t *testing.T) { + const bufSize = 5 + buf1 := make([]byte, 0, bufSize) + buf2 := make([]byte, 0, bufSize) + var runes []rune + for i := 0x20; i <= 0x7f; i++ { + runes = append(runes, rune(i)) + } + runes = append(runes, kelvin, smallLongEss) + + funcs := []struct { + name string + fold func(s, t []byte) bool + letter bool // must be ASCII letter + simple bool // must be simple ASCII letter (not 'S' or 'K') + }{ + { + name: "equalFoldRight", + fold: equalFoldRight, + }, + { + name: "asciiEqualFold", + fold: asciiEqualFold, + simple: true, + }, + { + name: "simpleLetterEqualFold", + fold: simpleLetterEqualFold, + simple: true, + letter: true, + }, + } + + for _, ff := range funcs { + for _, r := range runes { + if r >= utf8.RuneSelf { + continue + } + if ff.letter && !isASCIILetter(byte(r)) { + continue + } + if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') { + continue + } + for _, r2 := range runes { + buf1 := append(buf1[:0], 'x') + buf2 := append(buf2[:0], 'x') + buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)] + buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)] + buf1 = append(buf1, 'x') + buf2 = append(buf2, 'x') + want := bytes.EqualFold(buf1, buf2) + if got := ff.fold(buf1, buf2); got != want { + t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want) + } + } + } + } +} + +func isASCIILetter(b byte) bool { + return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry.go new file mode 100644 index 000000000..f554fbf8f --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry.go @@ -0,0 +1,56 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "fmt" + "time" + + gax "github.com/googleapis/gax-go" + + "golang.org/x/net/context" +) + +// Retry calls the supplied function f repeatedly according to the provided +// backoff parameters. It returns when one of the following occurs: +// When f's first return value is true, Retry immediately returns with f's second +// return value. +// When the provided context is done, Retry returns with an error that +// includes both ctx.Error() and the last error returned by f. +func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error { + return retry(ctx, bo, f, gax.Sleep) +} + +func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error), + sleep func(context.Context, time.Duration) error) error { + var lastErr error + for { + stop, err := f() + if stop { + return err + } + // Remember the last "real" error from f. + if err != nil && err != context.Canceled && err != context.DeadlineExceeded { + lastErr = err + } + p := bo.Pause() + if cerr := sleep(ctx, p); cerr != nil { + if lastErr != nil { + return fmt.Errorf("%v; last function err: %v", cerr, lastErr) + } + return cerr + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry_test.go new file mode 100644 index 000000000..590b55508 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/retry_test.go @@ -0,0 +1,64 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package internal + +import ( + "errors" + "testing" + "time" + + "golang.org/x/net/context" + + gax "github.com/googleapis/gax-go" +) + +func TestRetry(t *testing.T) { + ctx := context.Background() + // Without a context deadline, retry will run until the function + // says not to retry any more. + n := 0 + endRetry := errors.New("end retry") + err := retry(ctx, gax.Backoff{}, + func() (bool, error) { + n++ + if n < 10 { + return false, nil + } + return true, endRetry + }, + func(context.Context, time.Duration) error { return nil }) + if got, want := err, endRetry; got != want { + t.Errorf("got %v, want %v", err, endRetry) + } + if n != 10 { + t.Errorf("n: got %d, want %d", n, 10) + } + + // If the context has a deadline, sleep will return an error + // and end the function. + n = 0 + err = retry(ctx, gax.Backoff{}, + func() (bool, error) { return false, nil }, + func(context.Context, time.Duration) error { + n++ + if n < 10 { + return nil + } + return context.DeadlineExceeded + }) + if err == nil { + t.Error("got nil, want error") + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext.go new file mode 100644 index 000000000..bfc77ba38 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext.go @@ -0,0 +1,83 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package tracecontext provides encoders and decoders for Stackdriver Trace contexts. +package tracecontext + +import "encoding/binary" + +const ( + versionID = 0 + traceIDField = 0 + spanIDField = 1 + optsField = 2 + + traceIDLen = 16 + spanIDLen = 8 + optsLen = 1 + + // Len represents the length of trace context. + Len = 1 + 1 + traceIDLen + 1 + spanIDLen + 1 + optsLen +) + +// Encode encodes trace ID, span ID and options into dst. The number of bytes +// written will be returned. If len(dst) isn't big enough to fit the trace context, +// a negative number is returned. +func Encode(dst []byte, traceID []byte, spanID uint64, opts byte) (n int) { + if len(dst) < Len { + return -1 + } + var offset = 0 + putByte := func(b byte) { dst[offset] = b; offset++ } + putUint64 := func(u uint64) { binary.LittleEndian.PutUint64(dst[offset:], u); offset += 8 } + + putByte(versionID) + putByte(traceIDField) + for _, b := range traceID { + putByte(b) + } + putByte(spanIDField) + putUint64(spanID) + putByte(optsField) + putByte(opts) + + return offset +} + +// Decode decodes the src into a trace ID, span ID and options. If src doesn't +// contain a valid trace context, ok = false is returned. +func Decode(src []byte) (traceID []byte, spanID uint64, opts byte, ok bool) { + if len(src) < Len { + return traceID, spanID, 0, false + } + var offset = 0 + readByte := func() byte { b := src[offset]; offset++; return b } + readUint64 := func() uint64 { v := binary.LittleEndian.Uint64(src[offset:]); offset += 8; return v } + + if readByte() != versionID { + return traceID, spanID, 0, false + } + for offset < len(src) { + switch readByte() { + case traceIDField: + traceID = src[offset : offset+traceIDLen] + offset += traceIDLen + case spanIDField: + spanID = readUint64() + case optsField: + opts = readByte() + } + } + return traceID, spanID, opts, true +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext_test.go new file mode 100644 index 000000000..09aff7809 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/tracecontext/tracecontext_test.go @@ -0,0 +1,135 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tracecontext + +import ( + "reflect" + "testing" +) + +var validData = []byte{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, 101, 102, 103, 104, 2, 1} + +func TestDecode(t *testing.T) { + tests := []struct { + name string + data []byte + wantTraceID []byte + wantSpanID uint64 + wantOpts byte + wantOk bool + }{ + { + name: "nil data", + data: nil, + wantTraceID: nil, + wantSpanID: 0, + wantOpts: 0, + wantOk: false, + }, + { + name: "short data", + data: []byte{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77}, + wantTraceID: nil, + wantSpanID: 0, + wantOpts: 0, + wantOk: false, + }, + { + name: "wrong field number", + data: []byte{0, 1, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77}, + wantTraceID: nil, + wantSpanID: 0, + wantOpts: 0, + wantOk: false, + }, + { + name: "valid data", + data: validData, + wantTraceID: []byte{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}, + wantSpanID: 0x6867666564636261, + wantOpts: 1, + wantOk: true, + }, + } + for _, tt := range tests { + gotTraceID, gotSpanID, gotOpts, gotOk := Decode(tt.data) + if !reflect.DeepEqual(gotTraceID, tt.wantTraceID) { + t.Errorf("%s: Decode() gotTraceID = %v, want %v", tt.name, gotTraceID, tt.wantTraceID) + } + if gotSpanID != tt.wantSpanID { + t.Errorf("%s: Decode() gotSpanID = %v, want %v", tt.name, gotSpanID, tt.wantSpanID) + } + if gotOpts != tt.wantOpts { + t.Errorf("%s: Decode() gotOpts = %v, want %v", tt.name, gotOpts, tt.wantOpts) + } + if gotOk != tt.wantOk { + t.Errorf("%s: Decode() gotOk = %v, want %v", tt.name, gotOk, tt.wantOk) + } + } +} + +func TestEncode(t *testing.T) { + tests := []struct { + name string + dst []byte + traceID []byte + spanID uint64 + opts byte + wantN int + wantData []byte + }{ + { + name: "short data", + dst: make([]byte, 0), + traceID: []byte("00112233445566"), + spanID: 0x6867666564636261, + opts: 1, + wantN: -1, + wantData: make([]byte, 0), + }, + { + name: "valid data", + dst: make([]byte, Len), + traceID: []byte{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79}, + spanID: 0x6867666564636261, + opts: 1, + wantN: Len, + wantData: validData, + }, + } + for _, tt := range tests { + gotN := Encode(tt.dst, tt.traceID, tt.spanID, tt.opts) + if gotN != tt.wantN { + t.Errorf("%s: n = %v, want %v", tt.name, gotN, tt.wantN) + } + if gotData := tt.dst; !reflect.DeepEqual(gotData, tt.wantData) { + t.Errorf("%s: dst = %v, want %v", tt.name, gotData, tt.wantData) + } + } +} + +func BenchmarkDecode(b *testing.B) { + for i := 0; i < b.N; i++ { + Decode(validData) + } +} + +func BenchmarkEncode(b *testing.B) { + for i := 0; i < b.N; i++ { + traceID := make([]byte, 16) + var opts byte + Encode(validData, traceID, 0, opts) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/update_version.sh b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/update_version.sh new file mode 100755 index 000000000..fecf1f03f --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/update_version.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +today=$(date +%Y%m%d) + +sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE + diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version.go new file mode 100644 index 000000000..5eb06bac5 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version.go @@ -0,0 +1,71 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:generate ./update_version.sh + +// Package version contains version information for Google Cloud Client +// Libraries for Go, as reported in request headers. +package version + +import ( + "runtime" + "strings" + "unicode" +) + +// Repo is the current version of the client libraries in this +// repo. It should be a date in YYYYMMDD format. +const Repo = "20170621" + +// Go returns the Go runtime version. The returned string +// has no whitespace. +func Go() string { + return goVersion +} + +var goVersion = goVer(runtime.Version()) + +const develPrefix = "devel +" + +func goVer(s string) string { + if strings.HasPrefix(s, develPrefix) { + s = s[len(develPrefix):] + if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 { + s = s[:p] + } + return s + } + + if strings.HasPrefix(s, "go1") { + s = s[2:] + var prerelease string + if p := strings.IndexFunc(s, notSemverRune); p >= 0 { + s, prerelease = s[:p], s[p:] + } + if strings.HasSuffix(s, ".") { + s += "0" + } else if strings.Count(s, ".") < 2 { + s += ".0" + } + if prerelease != "" { + s += "-" + prerelease + } + return s + } + return "" +} + +func notSemverRune(r rune) bool { + return strings.IndexRune("0123456789.", r) < 0 +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version_test.go new file mode 100644 index 000000000..7e032f05b --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/internal/version/version_test.go @@ -0,0 +1,35 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import "testing" + +func TestGoVer(t *testing.T) { + for _, tst := range []struct { + in, want string + }{ + {"go1.8", "1.8.0"}, + {"go1.7.3", "1.7.3"}, + {"go1.8.typealias", "1.8.0-typealias"}, + {"go1.8beta1", "1.8.0-beta1"}, + {"go1.8rc2", "1.8.0-rc2"}, + {"devel +824f981dd4b7 Tue Apr 29 21:41:54 2014 -0400", "824f981dd4b7"}, + {"foo bar zipzap", ""}, + } { + if got := goVer(tst.in); got != tst.want { + t.Errorf("goVer(%q) = %q, want %q", tst.in, got, tst.want) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/key.json.enc b/examples/servers/reading-list/vendor/cloud.google.com/go/key.json.enc new file mode 100644 index 0000000000000000000000000000000000000000..a8978a9015b90ec780d8a0d55273622e373b94dc GIT binary patch literal 2432 zcmV-`34it-l&USX|M+{?-NqC3UwZP!9~cXufwcE;XsEQd>f+7?1R~ctV-!a7+I=7;AIX@R11L(kGXg4P*{S}4tQJMhHrGw*6GOwCE4y6eW$MCwt`LeioePo!slde= zh?{$Gq}3vGyQ$6svV5~iS3zKx!LR|2ec5CkLQizAqGEzt-fk_`+s5&e@WHf6rjd{k zhZO$-U_iXQxa1K=VZZ-qw|dg6hYi>SY@ery+&t@>k9`JGb`K^UJyz>+p(cxCxbDos zJ2^shF~*(w_9xPrHE7`&`Vc`{nHO{>FlRq#&`?Xbt51Jx_(uGAk5{;=sU!4|0(Y4Re6ygl)AYVZi-z$$+Nh;_&D!#ei6?@}SrR<-mX{pxlznb#yqF%3k zdzWnItjjb4Qklw=>U!IZAg3}6!-k0Auptx{p0(uuSPQwhk+p6DlH&^wD^<%%-AMTP zJ_fGnefVaGd^|I}f^o-ZaHl@%765s$(qopGyWX-&6!L4Z6WZj5j8|ZQKGM^$|NgHE zMOsn)CWY9sXB3ZoyZtPiE@Pk6cCku-Y7Z&M8?V9Zy!eJ>%GvHVhX-|BcPas=0FWYm z0v{;q{Es$vc1MCTlaXq>HY-XY*6v9$hRb3>GESvV7S?fLBP@risR&+jIc;v$Utcy& z9)Zkg_*F4evBya$R5O8uC0Vlq6?%vsIo!hXa8pa=aXcF2iGugq@|NU7#$z9Y_}?#4 z+Q|7g@046Hy@Vif%9qLPth@k*UN5U2xCCW4A97>f;wCq8_ReQayY^b-&;PFZEUa@? zzK+Nl>yl+*dG4J0s)uzQZ^{e-XiO==L(OMzKRvxr7gaP%fw3b_)T5aszA8*|eqZF6 zmE%(=t8R`_M=ELV^D7L^SAeve9u~`c=*~$0#9HK)J>J)*LIh5G)aC)mrLiZMedEELl(V76RL!E1^<6RJ=fZ2c#@L3G{z$0ZY%c0 zb%Lvg)z{fZc7S2jww$GYi3_lBy#s235Gs~bhASijhN*QMVvisFnQL`NlR$EWfP8$9 zMum#U9W{YC=2*WhK6GU_2;JbGGeYYzEeSGcBieLH;+{5o8C7*}hmlBxPW;NS zQ;hR_=67B;gIdb^27UN=wx|TsDQ%e+V(Df5K~+PtJd|y&8@f{q^qnxCn-XM2-{eCC zRKS9WrrM;F*CQT-HAoQUA-konS8x(SAF(bi{2XR?0=*}?HtczIOsB zV?Vi-NgEw_h_J`$sWTg840|hJxfz@&QMy$?4+t3_3GIvxIlA44zRJFQkR(NW`d(u# z(HZ~e#@X4g_bO*@91#dvMBpT)lttOQ7)IyYy{V*BfqrKDZr#~0wJuuVmn7OaSb{qs zs`>+nc3)>CLC~z%pBrsPFn!TTMiffrJ|#rDtB>2e9G{r~s(JnHm&nfL%Rt;2V4W|U zYD(Q<$o3L!U$C7Qo+>Ts5`*i5-dj?cZRGDcI6U&Si|<#` zr~<>bOl?t!;WwUW&ueqUoGCC&u_NJ;v;SnojnXiT-hjetj^_N_5Z|(Ze_U9vVw|vCU=o$6v(?S1CcZYkR z$@aRKnohGv8Q$_bC^;rsNdz2e6#jd|J0MX8zR%F2D=pKh&KA-04}9zzzH+=V4Gr++ zqa7%vm3)y>bRFDP5>AL%P+X5T^HM^|Rjqz5REcDKm0IQnC|{;~dP&uTTz~>K-PF8y!Hmg6Zkl$cw03|2pm+;-a;bAfli+ z&-iUS`7V!Y6g#(wva_F9+^k}xOv-2#Ni1}cK2>3+b{}O8o=vo@QQ7fU);-F`2`^ez zJTxA8Xr2u0oz`_J5wM^0v|XKf*3}By`(=0}%GoLvZX7@e^j}`+FJW~p4_`+7-`op< zZ;lk$vMIGij23O*6{bCy?Y~>7a+!dlTe!jT^(E9!00f6f?`s?8l&}q84bx=#^lALC zkhgzwsjay`v*QNB+qP_e4Eb14DWlb;AqG7O!n#D@W0x z6qF%kXj}47Hu{&drkfWUIZKF{_fn9h&MHd~0Exj*MXAo_)*ZO#-`470~ zidlpg0N%7mJC2w)fEJ!PxKdkY2vS~t!UR8f1?`rbbcbGM#KHAp{Vncervc0a&c{=o zctu%Rrz}V&?1-IMs#pxCQyEOUGf0=Hs<;z6I3Lwjt(+Q_@rBPg%G=olwJ$-U z0r{)fb+>}CEI-aLrO0s3&BH^9eI8+2~I4z9-ZE@mM;^ zdQ__V12IU*3Dqs1CxbHG>alvUuot9QbwOwG=Cz$i<;MDk;anv>y_pKoLQ@M0v0rh0 yqfml!vBS0FfZA^RTvj0s;vvF6)Hy|dAPY2Pg4{1K_(ia8pTApJ#MvjcIq}{n62c4s literal 0 HcmV?d00001 diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/license_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/license_test.go new file mode 100644 index 000000000..4b87878a0 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/license_test.go @@ -0,0 +1,70 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloud + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +var sentinels = []string{ + "Copyright", + "Google Inc", + `Licensed under the Apache License, Version 2.0 (the "License");`, +} + +func TestLicense(t *testing.T) { + err := filepath.Walk(".", func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + if ext := filepath.Ext(path); ext != ".go" && ext != ".proto" { + return nil + } + if strings.HasSuffix(path, ".pb.go") { + // .pb.go files are generated from the proto files. + // .proto files must have license headers. + return nil + } + if path == "bigtable/cmd/cbt/cbtdoc.go" { + // Automatically generated. + return nil + } + + src, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + src = src[:140] // Ensure all of the sentinel values are at the top of the file. + + // Find license + for _, sentinel := range sentinels { + if !bytes.Contains(src, []byte(sentinel)) { + t.Errorf("%v: license header not present. want %q", path, sentinel) + return nil + } + } + + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/old-news.md b/examples/servers/reading-list/vendor/cloud.google.com/go/old-news.md new file mode 100644 index 000000000..6a4402a87 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/old-news.md @@ -0,0 +1,435 @@ +_December 12, 2016_ + +Beta release of BigQuery, DataStore, Logging and Storage. See the +[blog post](https://cloudplatform.googleblog.com/2016/12/announcing-new-google-cloud-client.html). + +Also, BigQuery now supports structs. Read a row directly into a struct with +`RowIterator.Next`, and upload a row directly from a struct with `Uploader.Put`. +You can also use field tags. See the [package documentation][cloud-bigquery-ref] +for details. + +_December 5, 2016_ + +More changes to BigQuery: + +* The `ValueList` type was removed. It is no longer necessary. Instead of + ```go + var v ValueList + ... it.Next(&v) .. + ``` + use + + ```go + var v []Value + ... it.Next(&v) ... + ``` + +* Previously, repeatedly calling `RowIterator.Next` on the same `[]Value` or + `ValueList` would append to the slice. Now each call resets the size to zero first. + +* Schema inference will infer the SQL type BYTES for a struct field of + type []byte. Previously it inferred STRING. + +* The types `uint`, `uint64` and `uintptr` are no longer supported in schema + inference. BigQuery's integer type is INT64, and those types may hold values + that are not correctly represented in a 64-bit signed integer. + +* The SQL types DATE, TIME and DATETIME are now supported. They correspond to + the `Date`, `Time` and `DateTime` types in the new `cloud.google.com/go/civil` + package. + +_November 17, 2016_ + +Change to BigQuery: values from INTEGER columns will now be returned as int64, +not int. This will avoid errors arising from large values on 32-bit systems. + +_November 8, 2016_ + +New datastore feature: datastore now encodes your nested Go structs as Entity values, +instead of a flattened list of the embedded struct's fields. +This means that you may now have twice-nested slices, eg. +```go +type State struct { + Cities []struct{ + Populations []int + } +} +``` + +See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/79jtrdeuJAg) for +more details. + +_November 8, 2016_ + +Breaking changes to datastore: contexts no longer hold namespaces; instead you +must set a key's namespace explicitly. Also, key functions have been changed +and renamed. + +* The WithNamespace function has been removed. To specify a namespace in a Query, use the Query.Namespace method: + ```go + q := datastore.NewQuery("Kind").Namespace("ns") + ``` + +* All the fields of Key are exported. That means you can construct any Key with a struct literal: + ```go + k := &Key{Kind: "Kind", ID: 37, Namespace: "ns"} + ``` + +* As a result of the above, the Key methods Kind, ID, d.Name, Parent, SetParent and Namespace have been removed. + +* `NewIncompleteKey` has been removed, replaced by `IncompleteKey`. Replace + ```go + NewIncompleteKey(ctx, kind, parent) + ``` + with + ```go + IncompleteKey(kind, parent) + ``` + and if you do use namespaces, make sure you set the namespace on the returned key. + +* `NewKey` has been removed, replaced by `NameKey` and `IDKey`. Replace + ```go + NewKey(ctx, kind, name, 0, parent) + NewKey(ctx, kind, "", id, parent) + ``` + with + ```go + NameKey(kind, name, parent) + IDKey(kind, id, parent) + ``` + and if you do use namespaces, make sure you set the namespace on the returned key. + +* The `Done` variable has been removed. Replace `datastore.Done` with `iterator.Done`, from the package `google.golang.org/api/iterator`. + +* The `Client.Close` method will have a return type of error. It will return the result of closing the underlying gRPC connection. + +See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/hqXtM_4Ix-0) for +more details. + +_October 27, 2016_ + +Breaking change to bigquery: `NewGCSReference` is now a function, +not a method on `Client`. + +New bigquery feature: `Table.LoaderFrom` now accepts a `ReaderSource`, enabling +loading data into a table from a file or any `io.Reader`. + +_October 21, 2016_ + +Breaking change to pubsub: removed `pubsub.Done`. + +Use `iterator.Done` instead, where `iterator` is the package +`google.golang.org/api/iterator`. + +_October 19, 2016_ + +Breaking changes to cloud.google.com/go/bigquery: + +* Client.Table and Client.OpenTable have been removed. + Replace + ```go + client.OpenTable("project", "dataset", "table") + ``` + with + ```go + client.DatasetInProject("project", "dataset").Table("table") + ``` + +* Client.CreateTable has been removed. + Replace + ```go + client.CreateTable(ctx, "project", "dataset", "table") + ``` + with + ```go + client.DatasetInProject("project", "dataset").Table("table").Create(ctx) + ``` + +* Dataset.ListTables have been replaced with Dataset.Tables. + Replace + ```go + tables, err := ds.ListTables(ctx) + ``` + with + ```go + it := ds.Tables(ctx) + for { + table, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + // TODO: Handle error. + } + // TODO: use table. + } + ``` + +* Client.Read has been replaced with Job.Read, Table.Read and Query.Read. + Replace + ```go + it, err := client.Read(ctx, job) + ``` + with + ```go + it, err := job.Read(ctx) + ``` + and similarly for reading from tables or queries. + +* The iterator returned from the Read methods is now named RowIterator. Its + behavior is closer to the other iterators in these libraries. It no longer + supports the Schema method; see the next item. + Replace + ```go + for it.Next(ctx) { + var vals ValueList + if err := it.Get(&vals); err != nil { + // TODO: Handle error. + } + // TODO: use vals. + } + if err := it.Err(); err != nil { + // TODO: Handle error. + } + ``` + with + ``` + for { + var vals ValueList + err := it.Next(&vals) + if err == iterator.Done { + break + } + if err != nil { + // TODO: Handle error. + } + // TODO: use vals. + } + ``` + Instead of the `RecordsPerRequest(n)` option, write + ```go + it.PageInfo().MaxSize = n + ``` + Instead of the `StartIndex(i)` option, write + ```go + it.StartIndex = i + ``` + +* ValueLoader.Load now takes a Schema in addition to a slice of Values. + Replace + ```go + func (vl *myValueLoader) Load(v []bigquery.Value) + ``` + with + ```go + func (vl *myValueLoader) Load(v []bigquery.Value, s bigquery.Schema) + ``` + + +* Table.Patch is replace by Table.Update. + Replace + ```go + p := table.Patch() + p.Description("new description") + metadata, err := p.Apply(ctx) + ``` + with + ```go + metadata, err := table.Update(ctx, bigquery.TableMetadataToUpdate{ + Description: "new description", + }) + ``` + +* Client.Copy is replaced by separate methods for each of its four functions. + All options have been replaced by struct fields. + + * To load data from Google Cloud Storage into a table, use Table.LoaderFrom. + + Replace + ```go + client.Copy(ctx, table, gcsRef) + ``` + with + ```go + table.LoaderFrom(gcsRef).Run(ctx) + ``` + Instead of passing options to Copy, set fields on the Loader: + ```go + loader := table.LoaderFrom(gcsRef) + loader.WriteDisposition = bigquery.WriteTruncate + ``` + + * To extract data from a table into Google Cloud Storage, use + Table.ExtractorTo. Set fields on the returned Extractor instead of + passing options. + + Replace + ```go + client.Copy(ctx, gcsRef, table) + ``` + with + ```go + table.ExtractorTo(gcsRef).Run(ctx) + ``` + + * To copy data into a table from one or more other tables, use + Table.CopierFrom. Set fields on the returned Copier instead of passing options. + + Replace + ```go + client.Copy(ctx, dstTable, srcTable) + ``` + with + ```go + dst.Table.CopierFrom(srcTable).Run(ctx) + ``` + + * To start a query job, create a Query and call its Run method. Set fields + on the query instead of passing options. + + Replace + ```go + client.Copy(ctx, table, query) + ``` + with + ```go + query.Run(ctx) + ``` + +* Table.NewUploader has been renamed to Table.Uploader. Instead of options, + configure an Uploader by setting its fields. + Replace + ```go + u := table.NewUploader(bigquery.UploadIgnoreUnknownValues()) + ``` + with + ```go + u := table.NewUploader(bigquery.UploadIgnoreUnknownValues()) + u.IgnoreUnknownValues = true + ``` + +_October 10, 2016_ + +Breaking changes to cloud.google.com/go/storage: + +* AdminClient replaced by methods on Client. + Replace + ```go + adminClient.CreateBucket(ctx, bucketName, attrs) + ``` + with + ```go + client.Bucket(bucketName).Create(ctx, projectID, attrs) + ``` + +* BucketHandle.List replaced by BucketHandle.Objects. + Replace + ```go + for query != nil { + objs, err := bucket.List(d.ctx, query) + if err != nil { ... } + query = objs.Next + for _, obj := range objs.Results { + fmt.Println(obj) + } + } + ``` + with + ```go + iter := bucket.Objects(d.ctx, query) + for { + obj, err := iter.Next() + if err == iterator.Done { + break + } + if err != nil { ... } + fmt.Println(obj) + } + ``` + (The `iterator` package is at `google.golang.org/api/iterator`.) + + Replace `Query.Cursor` with `ObjectIterator.PageInfo().Token`. + + Replace `Query.MaxResults` with `ObjectIterator.PageInfo().MaxSize`. + + +* ObjectHandle.CopyTo replaced by ObjectHandle.CopierFrom. + Replace + ```go + attrs, err := src.CopyTo(ctx, dst, nil) + ``` + with + ```go + attrs, err := dst.CopierFrom(src).Run(ctx) + ``` + + Replace + ```go + attrs, err := src.CopyTo(ctx, dst, &storage.ObjectAttrs{ContextType: "text/html"}) + ``` + with + ```go + c := dst.CopierFrom(src) + c.ContextType = "text/html" + attrs, err := c.Run(ctx) + ``` + +* ObjectHandle.ComposeFrom replaced by ObjectHandle.ComposerFrom. + Replace + ```go + attrs, err := dst.ComposeFrom(ctx, []*storage.ObjectHandle{src1, src2}, nil) + ``` + with + ```go + attrs, err := dst.ComposerFrom(src1, src2).Run(ctx) + ``` + +* ObjectHandle.Update's ObjectAttrs argument replaced by ObjectAttrsToUpdate. + Replace + ```go + attrs, err := obj.Update(ctx, &storage.ObjectAttrs{ContextType: "text/html"}) + ``` + with + ```go + attrs, err := obj.Update(ctx, storage.ObjectAttrsToUpdate{ContextType: "text/html"}) + ``` + +* ObjectHandle.WithConditions replaced by ObjectHandle.If. + Replace + ```go + obj.WithConditions(storage.Generation(gen), storage.IfMetaGenerationMatch(mgen)) + ``` + with + ```go + obj.Generation(gen).If(storage.Conditions{MetagenerationMatch: mgen}) + ``` + + Replace + ```go + obj.WithConditions(storage.IfGenerationMatch(0)) + ``` + with + ```go + obj.If(storage.Conditions{DoesNotExist: true}) + ``` + +* `storage.Done` replaced by `iterator.Done` (from package `google.golang.org/api/iterator`). + +_October 6, 2016_ + +Package preview/logging deleted. Use logging instead. + +_September 27, 2016_ + +Logging client replaced with preview version (see below). + +_September 8, 2016_ + +* New clients for some of Google's Machine Learning APIs: Vision, Speech, and +Natural Language. + +* Preview version of a new [Stackdriver Logging][cloud-logging] client in +[`cloud.google.com/go/preview/logging`](https://godoc.org/cloud.google.com/go/preview/logging). +This client uses gRPC as its transport layer, and supports log reading, sinks +and metrics. It will replace the current client at `cloud.google.com/go/logging` shortly. + diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/run-tests.sh b/examples/servers/reading-list/vendor/cloud.google.com/go/run-tests.sh new file mode 100755 index 000000000..ac0b66a7a --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/run-tests.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Selectively run tests for this repo, based on what has changed +# in a commit. Runs short tests for the whole repo, and full tests +# for changed directories. + +set -e + +prefix=cloud.google.com/go + +dryrun=false +if [[ $1 == "-n" ]]; then + dryrun=true + shift +fi + +if [[ $1 == "" ]]; then + echo >&2 "usage: $0 [-n] COMMIT" + exit 1 +fi + +# Files or directories that cause all tests to run if modified. +declare -A run_all +run_all=([.travis.yml]=1 [run-tests.sh]=1) + +function run { + if $dryrun; then + echo $* + else + (set -x; $*) + fi +} + + +# Find all the packages that have changed in this commit. +declare -A changed_packages + +for f in $(git diff-tree --no-commit-id --name-only -r $1); do + if [[ ${run_all[$f]} == 1 ]]; then + # This change requires a full test. Do it and exit. + run go test -race -v $prefix/... + exit + fi + # Map, e.g., "spanner/client.go" to "$prefix/spanner". + d=$(dirname $f) + if [[ $d == "." ]]; then + pkg=$prefix + else + pkg=$prefix/$d + fi + changed_packages[$pkg]=1 +done + +echo "changed packages: ${!changed_packages[*]}" + + +# Reports whether its argument, a package name, depends (recursively) +# on a changed package. +function depends_on_changed_package { + # According to go list, a package does not depend on itself, so + # we test that separately. + if [[ ${changed_packages[$1]} == 1 ]]; then + return 0 + fi + for dep in $(go list -f '{{range .Deps}}{{.}} {{end}}' $1); do + if [[ ${changed_packages[$dep]} == 1 ]]; then + return 0 + fi + done + return 1 +} + +# Collect the packages into two separate lists. (It is faster go test a list of +# packages than to individually go test each one.) + +shorts= +fulls= +for pkg in $(go list $prefix/...); do # for each package in the repo + if depends_on_changed_package $pkg; then # if it depends on a changed package + fulls="$fulls $pkg" # run the full test + else # otherwise + shorts="$shorts $pkg" # run the short test + fi +done +run go test -race -v -short $shorts +run go test -race -v $fulls diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc.go new file mode 100644 index 000000000..ca7b46436 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc.go @@ -0,0 +1,95 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "encoding/hex" + "fmt" + + "cloud.google.com/go/internal/tracecontext" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +const grpcMetadataKey = "grpc-trace-bin" + +// GRPCClientInterceptor returns a grpc.UnaryClientInterceptor that traces all outgoing requests from a gRPC client. +// The calling context should already have a *trace.Span; a child span will be +// created for the outgoing gRPC call. If the calling context doesn't have a span, +// the call will not be traced. +// +// The functionality in gRPC that this feature relies on is currently experimental. +func (c *Client) GRPCClientInterceptor() grpc.UnaryClientInterceptor { + return grpc.UnaryClientInterceptor(c.grpcUnaryInterceptor) +} + +func (c *Client) grpcUnaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + // TODO: also intercept streams. + span := FromContext(ctx).NewChild(method) + if span == nil { + span = c.NewSpan(method) + } + defer span.Finish() + + traceContext := make([]byte, tracecontext.Len) + // traceID is a hex-encoded 128-bit value. + // TODO(jbd): Decode trace IDs upon arrival and + // represent trace IDs with 16 bytes internally. + tid, err := hex.DecodeString(span.trace.traceID) + if err != nil { + return invoker(ctx, method, req, reply, cc, opts...) + } + tracecontext.Encode(traceContext, tid, span.span.SpanId, byte(span.trace.globalOptions)) + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.Pairs(grpcMetadataKey, string(traceContext)) + } else { + md = md.Copy() // metadata is immutable, copy. + md[grpcMetadataKey] = []string{string(traceContext)} + } + ctx = metadata.NewOutgoingContext(ctx, md) + + err = invoker(ctx, method, req, reply, cc, opts...) + if err != nil { + // TODO: standardize gRPC label names? + span.SetLabel("error", err.Error()) + } + return err +} + +// GRPCServerInterceptor returns a grpc.UnaryServerInterceptor that enables the tracing of the incoming +// gRPC calls. Incoming call's context can be used to extract the span on servers that enabled this option: +// +// span := trace.FromContext(ctx) +// +// The functionality in gRPC that this feature relies on is currently experimental. +func (c *Client) GRPCServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + md, _ := metadata.FromIncomingContext(ctx) + var traceHeader string + if header, ok := md[grpcMetadataKey]; ok { + traceID, spanID, opts, ok := tracecontext.Decode([]byte(header[0])) + if ok { + // TODO(jbd): Generate a span directly from string(traceID), spanID and opts. + traceHeader = fmt.Sprintf("%x/%d;o=%d", traceID, spanID, opts) + } + } + span := c.SpanFromHeader(info.FullMethod, traceHeader) + defer span.Finish() + ctx = NewContext(ctx, span) + return handler(ctx, req) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc_test.go new file mode 100644 index 000000000..afb9d08ef --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/grpc_test.go @@ -0,0 +1,178 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "io/ioutil" + "log" + "net" + "net/http" + "strings" + "testing" + + pb "cloud.google.com/go/trace/testdata/helloworld" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +func TestGRPCInterceptors(t *testing.T) { + tc := newTestClient(&noopTransport{}) + + // default sampling with global=1. + parent := tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1") + testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if got, want := in.TraceID(), out.TraceID(); got != want { + t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want) + } + if !in.Traced() { + t.Errorf("incoming span is not traced; want traced") + } + }) + + // default sampling with global=0. + parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=0") + testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if got, want := in.TraceID(), out.TraceID(); got != want { + t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want) + } + if in.Traced() { + t.Errorf("incoming span is traced; want not traced") + } + }) + + // sampling all with global=1. + all, _ := NewLimitedSampler(1.0, 1<<32) + tc.SetSamplingPolicy(all) + parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1") + testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if got, want := in.TraceID(), out.TraceID(); got != want { + t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want) + } + if !in.Traced() { + t.Errorf("incoming span is not traced; want traced") + } + }) + + // sampling none with global=1. + none, _ := NewLimitedSampler(0, 0) + tc.SetSamplingPolicy(none) + parent = tc.SpanFromHeader("parent", "7f27601f17b7a2873739efd18ff83872/123;o=1") + testGRPCInterceptor(t, tc, parent, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if got, want := in.TraceID(), out.TraceID(); got != want { + t.Errorf("incoming call is not tracing the outgoing trace; TraceID = %q; want %q", got, want) + } + if in.Traced() { + t.Errorf("incoming span is traced; want not traced") + } + }) + + // sampling all with no parent span. + tc.SetSamplingPolicy(all) + testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if in.TraceID() == "" { + t.Errorf("incoming call TraceID is empty") + } + if !in.Traced() { + t.Errorf("incoming span is not traced; want traced") + } + }) + + // sampling none with no parent span. + tc.SetSamplingPolicy(none) + testGRPCInterceptor(t, tc, nil, func(t *testing.T, out, in *Span) { + if in == nil { + t.Fatalf("missing span in the incoming context") + } + if in.TraceID() == "" { + t.Errorf("incoming call TraceID is empty") + } + if in.Traced() { + t.Errorf("incoming span is traced; want not traced") + } + }) +} + +func testGRPCInterceptor(t *testing.T, tc *Client, parent *Span, assert func(t *testing.T, out, in *Span)) { + incomingCh := make(chan *Span, 1) + addrCh := make(chan net.Addr, 1) + go func() { + lis, err := net.Listen("tcp", "") + if err != nil { + t.Fatalf("Failed to listen: %v", err) + } + addrCh <- lis.Addr() + + s := grpc.NewServer(grpc.UnaryInterceptor(tc.GRPCServerInterceptor())) + pb.RegisterGreeterServer(s, &grpcServer{ + fn: func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + incomingCh <- FromContext(ctx) + return &pb.HelloReply{}, nil + }, + }) + if err := s.Serve(lis); err != nil { + t.Fatalf("Failed to serve: %v", err) + } + }() + + addr := <-addrCh + conn, err := grpc.Dial(addr.String(), grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor())) + if err != nil { + t.Fatalf("Did not connect: %v", err) + } + defer conn.Close() + c := pb.NewGreeterClient(conn) + + outgoingCtx := NewContext(context.Background(), parent) + _, err = c.SayHello(outgoingCtx, &pb.HelloRequest{}) + if err != nil { + log.Fatalf("Could not SayHello: %v", err) + } + + assert(t, parent, <-incomingCh) +} + +type noopTransport struct{} + +func (rt *noopTransport) RoundTrip(req *http.Request) (*http.Response, error) { + resp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("{}")), + } + return resp, nil +} + +type grpcServer struct { + fn func(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) +} + +func (s *grpcServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { + return s.fn(ctx, in) +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http.go new file mode 100644 index 000000000..e06fb5781 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http.go @@ -0,0 +1,105 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build go1.7 + +package trace + +import ( + "net/http" +) + +// Transport is an http.RoundTripper that traces the outgoing requests. +// +// Transport is safe for concurrent usage. +type Transport struct { + // Base is the base http.RoundTripper to be used to do the actual request. + // + // Optional. If nil, http.DefaultTransport is used. + Base http.RoundTripper +} + +// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers. +// The created span can follow a parent span, if a parent is presented in +// the request's context. +func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) { + span := FromContext(req.Context()).NewRemoteChild(req) + resp, err := t.base().RoundTrip(req) + + // TODO(jbd): Is it possible to defer the span.Finish? + // In cases where RoundTrip panics, we still can finish the span. + span.Finish(WithResponse(resp)) + return resp, err +} + +// CancelRequest cancels an in-flight request by closing its connection. +func (t Transport) CancelRequest(req *http.Request) { + type canceler interface { + CancelRequest(*http.Request) + } + if cr, ok := t.base().(canceler); ok { + cr.CancelRequest(req) + } +} + +func (t Transport) base() http.RoundTripper { + if t.Base != nil { + return t.Base + } + return http.DefaultTransport +} + +// HTTPHandler returns a http.Handler from the given handler +// that is aware of the incoming request's span. +// The span can be extracted from the incoming request in handler +// functions from incoming request's context: +// +// span := trace.FromContext(r.Context()) +// +// The span will be auto finished by the handler. +func (c *Client) HTTPHandler(h http.Handler) http.Handler { + return &handler{traceClient: c, handler: h} +} + +type handler struct { + traceClient *Client + handler http.Handler +} + +func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + traceID, parentSpanID, options, optionsOk, ok := traceInfoFromHeader(r.Header.Get(httpHeader)) + if !ok { + traceID = nextTraceID() + } + t := &trace{ + traceID: traceID, + client: h.traceClient, + globalOptions: options, + localOptions: options, + } + span := startNewChildWithRequest(r, t, parentSpanID) + span.span.Kind = spanKindServer + span.rootSpan = true + configureSpanFromPolicy(span, h.traceClient.policy, ok) + defer span.Finish() + + r = r.WithContext(NewContext(r.Context(), span)) + if ok && !optionsOk { + // Inject the trace context back to the response with the sampling options. + // TODO(jbd): Remove when there is a better way to report the client's sampling. + w.Header().Set(httpHeader, spanHeader(traceID, parentSpanID, span.trace.localOptions)) + } + h.handler.ServeHTTP(w, r) + +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http_test.go new file mode 100644 index 000000000..54c2e174e --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/http_test.go @@ -0,0 +1,151 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build go1.7 + +package trace + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +type recorderTransport struct { + ch chan *http.Request +} + +func (rt *recorderTransport) RoundTrip(req *http.Request) (*http.Response, error) { + rt.ch <- req + resp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("{}")), + } + return resp, nil +} + +func TestNewHTTPClient(t *testing.T) { + rt := &recorderTransport{ + ch: make(chan *http.Request, 1), + } + + tc := newTestClient(&noopTransport{}) + client := &http.Client{ + Transport: &Transport{ + Base: rt, + }, + } + req, _ := http.NewRequest("GET", "http://example.com", nil) + + t.Run("NoTrace", func(t *testing.T) { + _, err := client.Do(req) + if err != nil { + t.Error(err) + } + outgoing := <-rt.ch + if got, want := outgoing.Header.Get(httpHeader), ""; want != got { + t.Errorf("got trace header = %q; want none", got) + } + }) + + t.Run("Trace", func(t *testing.T) { + span := tc.NewSpan("/foo") + + req = req.WithContext(NewContext(req.Context(), span)) + _, err := client.Do(req) + if err != nil { + t.Error(err) + } + outgoing := <-rt.ch + + s := tc.SpanFromHeader("/foo", outgoing.Header.Get(httpHeader)) + if got, want := s.TraceID(), span.TraceID(); got != want { + t.Errorf("trace ID = %q; want %q", got, want) + } + }) +} + +func TestHTTPHandlerNoTrace(t *testing.T) { + tc := newTestClient(&noopTransport{}) + client := &http.Client{ + Transport: &Transport{}, + } + handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + span := FromContext(r.Context()) + if span == nil { + t.Errorf("span is nil; want non-nil span") + } + })) + + ts := httptest.NewServer(handler) + defer ts.Close() + + req, _ := http.NewRequest("GET", ts.URL, nil) + _, err := client.Do(req) + if err != nil { + t.Fatal(err) + } +} + +func TestHTTPHandler_response(t *testing.T) { + tc := newTestClient(&noopTransport{}) + p, _ := NewLimitedSampler(1, 1<<32) // all + tc.SetSamplingPolicy(p) + handler := tc.HTTPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + ts := httptest.NewServer(handler) + defer ts.Close() + + tests := []struct { + name string + traceHeader string + wantTraceHeader string + }{ + { + name: "no global", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/123", + wantTraceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1", + }, + { + name: "global=1", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=1", + wantTraceHeader: "", + }, + { + name: "global=0", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/123;o=0", + wantTraceHeader: "", + }, + { + name: "no trace context", + traceHeader: "", + wantTraceHeader: "", + }, + } + + for _, tt := range tests { + req, _ := http.NewRequest("GET", ts.URL, nil) + req.Header.Set(httpHeader, tt.traceHeader) + + res, err := http.DefaultClient.Do(req) + if err != nil { + t.Errorf("failed to request: %v", err) + } + if got, want := res.Header.Get(httpHeader), tt.wantTraceHeader; got != want { + t.Errorf("%v: response context header = %q; want %q", tt.name, got, want) + } + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/httpexample_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/httpexample_test.go new file mode 100644 index 000000000..4c2903642 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/httpexample_test.go @@ -0,0 +1,57 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build go1.7 + +package trace_test + +import ( + "log" + "net/http" + + "cloud.google.com/go/trace" +) + +var traceClient *trace.Client + +func ExampleHTTPClient_Do() { + client := http.Client{ + Transport: &trace.Transport{}, + } + span := traceClient.NewSpan("/foo") // traceClient is a *trace.Client + + req, _ := http.NewRequest("GET", "https://metadata/users", nil) + req = req.WithContext(trace.NewContext(req.Context(), span)) + + if _, err := client.Do(req); err != nil { + log.Fatal(err) + } +} + +func ExampleClient_HTTPHandler() { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + client := http.Client{ + Transport: &trace.Transport{}, + } + + req, _ := http.NewRequest("GET", "https://metadata/users", nil) + req = req.WithContext(r.Context()) + + // The outgoing request will be traced with r's trace ID. + if _, err := client.Do(req); err != nil { + log.Fatal(err) + } + }) + http.Handle("/foo", traceClient.HTTPHandler(handler)) // traceClient is a *trace.Client +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/sampling.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/sampling.go new file mode 100644 index 000000000..d609290b9 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/sampling.go @@ -0,0 +1,117 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + crand "crypto/rand" + "encoding/binary" + "fmt" + "math/rand" + "sync" + "time" + + "golang.org/x/time/rate" +) + +type SamplingPolicy interface { + // Sample returns a Decision. + // If Trace is false in the returned Decision, then the Decision should be + // the zero value. + Sample(p Parameters) Decision +} + +// Parameters contains the values passed to a SamplingPolicy's Sample method. +type Parameters struct { + HasTraceHeader bool // whether the incoming request has a valid X-Cloud-Trace-Context header. +} + +// Decision is the value returned by a call to a SamplingPolicy's Sample method. +type Decision struct { + Trace bool // Whether to trace the request. + Sample bool // Whether the trace is included in the random sample. + Policy string // Name of the sampling policy. + Weight float64 // Sample weight to be used in statistical calculations. +} + +type sampler struct { + fraction float64 + skipped float64 + *rate.Limiter + *rand.Rand + sync.Mutex +} + +func (s *sampler) Sample(p Parameters) Decision { + s.Lock() + x := s.Float64() + d := s.sample(p, time.Now(), x) + s.Unlock() + return d +} + +// sample contains the a deterministic, time-independent logic of Sample. +func (s *sampler) sample(p Parameters, now time.Time, x float64) (d Decision) { + d.Sample = x < s.fraction + d.Trace = p.HasTraceHeader || d.Sample + if !d.Trace { + // We have no reason to trace this request. + return Decision{} + } + // We test separately that the rate limit is not tiny before calling AllowN, + // because of overflow problems in x/time/rate. + if s.Limit() < 1e-9 || !s.AllowN(now, 1) { + // Rejected by the rate limit. + if d.Sample { + s.skipped++ + } + return Decision{} + } + if d.Sample { + d.Policy, d.Weight = "default", (1.0+s.skipped)/s.fraction + s.skipped = 0.0 + } + return +} + +// NewLimitedSampler returns a sampling policy that randomly samples a given +// fraction of requests. It also enforces a limit on the number of traces per +// second. It tries to trace every request with a trace header, but will not +// exceed the qps limit to do it. +func NewLimitedSampler(fraction, maxqps float64) (SamplingPolicy, error) { + if !(fraction >= 0) { + return nil, fmt.Errorf("invalid fraction %f", fraction) + } + if !(maxqps >= 0) { + return nil, fmt.Errorf("invalid maxqps %f", maxqps) + } + // Set a limit on the number of accumulated "tokens", to limit bursts of + // traced requests. Use one more than a second's worth of tokens, or 100, + // whichever is smaller. + // See https://godoc.org/golang.org/x/time/rate#NewLimiter. + maxTokens := 100 + if maxqps < 99.0 { + maxTokens = 1 + int(maxqps) + } + var seed int64 + if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil { + seed = time.Now().UnixNano() + } + s := sampler{ + fraction: fraction, + Limiter: rate.NewLimiter(rate.Limit(maxqps), maxTokens), + Rand: rand.New(rand.NewSource(seed)), + } + return &s, nil +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace.go new file mode 100644 index 000000000..9d4ab1dcb --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace.go @@ -0,0 +1,817 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package trace is a Google Stackdriver Trace library. +// +// This package is still experimental and subject to change. +// +// See https://cloud.google.com/trace/api/#data_model for a discussion of traces +// and spans. +// +// To initialize a client that connects to the Stackdriver Trace server, use the +// NewClient function. Generally you will want to do this on program +// initialization. +// +// import "cloud.google.com/go/trace" +// ... +// traceClient, err = trace.NewClient(ctx, projectID) +// +// Calling SpanFromRequest will create a new trace span for an incoming HTTP +// request. If the request contains a trace context header, it is used to +// determine the trace ID. Otherwise, a new trace ID is created. +// +// func handler(w http.ResponseWriter, r *http.Request) { +// span := traceClient.SpanFromRequest(r) +// defer span.Finish() +// ... +// } +// +// SpanFromRequest and NewSpan returns nil if the *Client is nil, so you can disable +// tracing by not initializing your *Client variable. All of the exported +// functions on *Span do nothing when the *Span is nil. +// +// If you need to start traces that don't correspond to an incoming HTTP request, +// you can use NewSpan to create a root-level span. +// +// span := traceClient.NewSpan("span name") +// defer span.Finish() +// +// Although a trace span object is created for every request, only a subset of +// traces are uploaded to the server, for efficiency. By default, the requests +// that are traced are those with the tracing bit set in the options field of +// the trace context header. Ideally, you should override this behaviour by +// calling SetSamplingPolicy. NewLimitedSampler returns an implementation of +// SamplingPolicy which traces requests that have the tracing bit set, and also +// randomly traces a specified fraction of requests. Additionally, it sets a +// limit on the number of requests traced per second. The following example +// traces one in every thousand requests, up to a limit of 5 per second. +// +// p, err := trace.NewLimitedSampler(0.001, 5) +// traceClient.SetSamplingPolicy(p) +// +// You can create a new span as a child of an existing span with NewChild. +// +// childSpan := span.NewChild(name) +// ... +// childSpan.Finish() +// +// When sending an HTTP request to another server, NewRemoteChild will create +// a span to represent the time the current program waits for the request to +// complete, and attach a header to the outgoing request so that the trace will +// be propagated to the destination server. +// +// childSpan := span.NewRemoteChild(&httpRequest) +// ... +// childSpan.Finish() +// +// Alternatively, if you have access to the X-Cloud-Trace-Context header value +// but not the underlying HTTP request (this can happen if you are using a +// different transport or messaging protocol, such as gRPC), you can use +// SpanFromHeader instead of SpanFromRequest. In that case, you will need to +// specify the span name explicility, since it cannot be constructed from the +// HTTP request's URL and method. +// +// func handler(r *somepkg.Request) { +// span := traceClient.SpanFromHeader("span name", r.TraceContext()) +// defer span.Finish() +// ... +// } +// +// Spans can contain a map from keys to values that have useful information +// about the span. The elements of this map are called labels. Some labels, +// whose keys all begin with the string "trace.cloud.google.com/", are set +// automatically in the following ways: +// +// - SpanFromRequest sets some labels to data about the incoming request. +// +// - NewRemoteChild sets some labels to data about the outgoing request. +// +// - Finish sets a label to a stack trace, if the stack trace option is enabled +// in the incoming trace header. +// +// - The WithResponse option sets some labels to data about a response. +// You can also set labels using SetLabel. If a label is given a value +// automatically and by SetLabel, the automatically-set value is used. +// +// span.SetLabel(key, value) +// +// The WithResponse option can be used when Finish is called. +// +// childSpan := span.NewRemoteChild(outgoingReq) +// resp, err := http.DefaultClient.Do(outgoingReq) +// ... +// childSpan.Finish(trace.WithResponse(resp)) +// +// When a span created by SpanFromRequest or SpamFromHeader is finished, the +// finished spans in the corresponding trace -- the span itself and its +// descendants -- are uploaded to the Stackdriver Trace server using the +// *Client that created the span. Finish returns immediately, and uploading +// occurs asynchronously. You can use the FinishWait function instead to wait +// until uploading has finished. +// +// err := span.FinishWait() +// +// Using contexts to pass *trace.Span objects through your program will often +// be a better approach than passing them around explicitly. This allows trace +// spans, and other request-scoped or part-of-request-scoped values, to be +// easily passed through API boundaries. Various Google Cloud libraries will +// retrieve trace spans from contexts and automatically create child spans for +// API requests. +// See https://blog.golang.org/context for more discussion of contexts. +// A derived context containing a trace span can be created using NewContext. +// +// span := traceClient.SpanFromRequest(r) +// ctx = trace.NewContext(ctx, span) +// +// The span can be retrieved from a context elsewhere in the program using +// FromContext. +// +// func foo(ctx context.Context) { +// span := trace.FromContext(ctx).NewChild("in foo") +// defer span.Finish() +// ... +// } +// +package trace // import "cloud.google.com/go/trace" + +import ( + "crypto/rand" + "encoding/binary" + "encoding/json" + "fmt" + "log" + "net/http" + "runtime" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "golang.org/x/net/context" + api "google.golang.org/api/cloudtrace/v1" + "google.golang.org/api/gensupport" + "google.golang.org/api/option" + "google.golang.org/api/support/bundler" + "google.golang.org/api/transport" +) + +const ( + httpHeader = `X-Cloud-Trace-Context` + userAgent = `gcloud-golang-trace/20160501` + cloudPlatformScope = `https://www.googleapis.com/auth/cloud-platform` + spanKindClient = `RPC_CLIENT` + spanKindServer = `RPC_SERVER` + spanKindUnspecified = `SPAN_KIND_UNSPECIFIED` + maxStackFrames = 20 + labelHost = `trace.cloud.google.com/http/host` + labelMethod = `trace.cloud.google.com/http/method` + labelStackTrace = `trace.cloud.google.com/stacktrace` + labelStatusCode = `trace.cloud.google.com/http/status_code` + labelURL = `trace.cloud.google.com/http/url` + labelSamplingPolicy = `trace.cloud.google.com/sampling_policy` + labelSamplingWeight = `trace.cloud.google.com/sampling_weight` +) + +const ( + // ScopeTraceAppend grants permissions to write trace data for a project. + ScopeTraceAppend = "https://www.googleapis.com/auth/trace.append" + + // ScopeCloudPlatform grants permissions to view and manage your data + // across Google Cloud Platform services. + ScopeCloudPlatform = "https://www.googleapis.com/auth/cloud-platform" +) + +type contextKey struct{} + +type stackLabelValue struct { + Frames []stackFrame `json:"stack_frame"` +} + +type stackFrame struct { + Class string `json:"class_name,omitempty"` + Method string `json:"method_name"` + Filename string `json:"file_name"` + Line int64 `json:"line_number"` +} + +var ( + spanIDCounter uint64 + spanIDIncrement uint64 +) + +func init() { + // Set spanIDCounter and spanIDIncrement to random values. nextSpanID will + // return an arithmetic progression using these values, skipping zero. We set + // the LSB of spanIDIncrement to 1, so that the cycle length is 2^64. + binary.Read(rand.Reader, binary.LittleEndian, &spanIDCounter) + binary.Read(rand.Reader, binary.LittleEndian, &spanIDIncrement) + spanIDIncrement |= 1 + // Attach hook for autogenerated Google API calls. This will automatically + // create trace spans for API calls if there is a trace in the context. + gensupport.RegisterHook(requestHook) +} + +func requestHook(ctx context.Context, req *http.Request) func(resp *http.Response) { + span := FromContext(ctx) + if span == nil || req == nil { + return nil + } + span = span.NewRemoteChild(req) + return func(resp *http.Response) { + if resp != nil { + span.Finish(WithResponse(resp)) + } else { + span.Finish() + } + } +} + +// nextSpanID returns a new span ID. It will never return zero. +func nextSpanID() uint64 { + var id uint64 + for id == 0 { + id = atomic.AddUint64(&spanIDCounter, spanIDIncrement) + } + return id +} + +// nextTraceID returns a new trace ID. +func nextTraceID() string { + id1 := nextSpanID() + id2 := nextSpanID() + return fmt.Sprintf("%016x%016x", id1, id2) +} + +// Client is a client for uploading traces to the Google Stackdriver Trace server. +type Client struct { + service *api.Service + projectID string + policy SamplingPolicy + bundler *bundler.Bundler +} + +// NewClient creates a new Google Stackdriver Trace client. +func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) { + o := []option.ClientOption{ + option.WithScopes(cloudPlatformScope), + option.WithUserAgent(userAgent), + } + o = append(o, opts...) + hc, basePath, err := transport.NewHTTPClient(ctx, o...) + if err != nil { + return nil, fmt.Errorf("creating HTTP client for Google Stackdriver Trace API: %v", err) + } + apiService, err := api.New(hc) + if err != nil { + return nil, fmt.Errorf("creating Google Stackdriver Trace API client: %v", err) + } + if basePath != "" { + // An option set a basepath, so override api.New's default. + apiService.BasePath = basePath + } + c := &Client{ + service: apiService, + projectID: projectID, + } + bundler := bundler.NewBundler((*api.Trace)(nil), func(bundle interface{}) { + traces := bundle.([]*api.Trace) + err := c.upload(traces) + if err != nil { + log.Printf("failed to upload %d traces to the Cloud Trace server: %v", len(traces), err) + } + }) + bundler.DelayThreshold = 2 * time.Second + bundler.BundleCountThreshold = 100 + // We're not measuring bytes here, we're counting traces and spans as one "byte" each. + bundler.BundleByteThreshold = 1000 + bundler.BundleByteLimit = 1000 + bundler.BufferedByteLimit = 10000 + c.bundler = bundler + return c, nil +} + +// SetSamplingPolicy sets the SamplingPolicy that determines how often traces +// are initiated by this client. +func (c *Client) SetSamplingPolicy(p SamplingPolicy) { + if c != nil { + c.policy = p + } +} + +// SpanFromHeader returns a new trace span, based on a provided request header +// value. See https://cloud.google.com/trace/docs/faq. +// +// It returns nil iff the client is nil. +// +// The trace information and identifiers will be read from the header value. +// Otherwise, a new trace ID is made and the parent span ID is zero. +// +// The name of the new span is provided as an argument. +// +// If a non-nil sampling policy has been set in the client, it can override +// the options set in the header and choose whether to trace the request. +// +// If the header doesn't have existing tracing information, then a *Span is +// returned anyway, but it will not be uploaded to the server, just as when +// calling SpanFromRequest on an untraced request. +// +// Most users using HTTP should use SpanFromRequest, rather than +// SpanFromHeader, since it provides additional functionality for HTTP +// requests. In particular, it will set various pieces of request information +// as labels on the *Span, which is not available from the header alone. +func (c *Client) SpanFromHeader(name string, header string) *Span { + if c == nil { + return nil + } + traceID, parentSpanID, options, _, ok := traceInfoFromHeader(header) + if !ok { + traceID = nextTraceID() + } + t := &trace{ + traceID: traceID, + client: c, + globalOptions: options, + localOptions: options, + } + span := startNewChild(name, t, parentSpanID) + span.span.Kind = spanKindServer + span.rootSpan = true + configureSpanFromPolicy(span, c.policy, ok) + return span +} + +// SpanFromRequest returns a new trace span for an HTTP request. +// +// It returns nil iff the client is nil. +// +// If the incoming HTTP request contains a trace context header, the trace ID, +// parent span ID, and tracing options will be read from that header. +// Otherwise, a new trace ID is made and the parent span ID is zero. +// +// If a non-nil sampling policy has been set in the client, it can override the +// options set in the header and choose whether to trace the request. +// +// If the request is not being traced, then a *Span is returned anyway, but it +// will not be uploaded to the server -- it is only useful for propagating +// trace context to child requests and for getting the TraceID. All its +// methods can still be called -- the Finish, FinishWait, and SetLabel methods +// do nothing. NewChild does nothing, and returns the same *Span. TraceID +// works as usual. +func (c *Client) SpanFromRequest(r *http.Request) *Span { + if c == nil { + return nil + } + traceID, parentSpanID, options, _, ok := traceInfoFromHeader(r.Header.Get(httpHeader)) + if !ok { + traceID = nextTraceID() + } + t := &trace{ + traceID: traceID, + client: c, + globalOptions: options, + localOptions: options, + } + span := startNewChildWithRequest(r, t, parentSpanID) + span.span.Kind = spanKindServer + span.rootSpan = true + configureSpanFromPolicy(span, c.policy, ok) + return span +} + +// NewSpan returns a new trace span with the given name. +// +// A new trace and span ID is generated to trace the span. +// Returned span need to be finished by calling Finish or FinishWait. +func (c *Client) NewSpan(name string) *Span { + if c == nil { + return nil + } + t := &trace{ + traceID: nextTraceID(), + client: c, + localOptions: optionTrace, + globalOptions: optionTrace, + } + span := startNewChild(name, t, 0) + span.span.Kind = spanKindUnspecified + span.rootSpan = true + configureSpanFromPolicy(span, c.policy, false) + return span +} + +func configureSpanFromPolicy(s *Span, p SamplingPolicy, ok bool) { + if p == nil { + return + } + d := p.Sample(Parameters{HasTraceHeader: ok}) + if d.Trace { + // Turn on tracing locally, and in child requests. + s.trace.localOptions |= optionTrace + s.trace.globalOptions |= optionTrace + } else { + // Turn off tracing locally. + s.trace.localOptions = 0 + return + } + if d.Sample { + // This trace is in the random sample, so set the labels. + s.SetLabel(labelSamplingPolicy, d.Policy) + s.SetLabel(labelSamplingWeight, fmt.Sprint(d.Weight)) + } +} + +// NewContext returns a derived context containing the span. +func NewContext(ctx context.Context, s *Span) context.Context { + if s == nil { + return ctx + } + return context.WithValue(ctx, contextKey{}, s) +} + +// FromContext returns the span contained in the context, or nil. +func FromContext(ctx context.Context) *Span { + s, _ := ctx.Value(contextKey{}).(*Span) + return s +} + +func traceInfoFromHeader(h string) (traceID string, spanID uint64, options optionFlags, optionsOk bool, ok bool) { + // See https://cloud.google.com/trace/docs/faq for the header format. + // Return if the header is empty or missing, or if the header is unreasonably + // large, to avoid making unnecessary copies of a large string. + if h == "" || len(h) > 200 { + return "", 0, 0, false, false + + } + + // Parse the trace id field. + slash := strings.Index(h, `/`) + if slash == -1 { + return "", 0, 0, false, false + + } + traceID, h = h[:slash], h[slash+1:] + + // Parse the span id field. + spanstr := h + semicolon := strings.Index(h, `;`) + if semicolon != -1 { + spanstr, h = h[:semicolon], h[semicolon+1:] + } + spanID, err := strconv.ParseUint(spanstr, 10, 64) + if err != nil { + return "", 0, 0, false, false + + } + + // Parse the options field, options field is optional. + if !strings.HasPrefix(h, "o=") { + return traceID, spanID, 0, false, true + + } + o, err := strconv.ParseUint(h[2:], 10, 64) + if err != nil { + return "", 0, 0, false, false + + } + options = optionFlags(o) + return traceID, spanID, options, true, true +} + +type optionFlags uint32 + +const ( + optionTrace optionFlags = 1 << iota + optionStack +) + +type trace struct { + mu sync.Mutex + client *Client + traceID string + globalOptions optionFlags // options that will be passed to any child requests + localOptions optionFlags // options applied in this server + spans []*Span // finished spans for this trace. +} + +// finish appends s to t.spans. If s is the root span, uploads the trace to the +// server. +func (t *trace) finish(s *Span, wait bool, opts ...FinishOption) error { + for _, o := range opts { + o.modifySpan(s) + } + s.end = time.Now() + t.mu.Lock() + t.spans = append(t.spans, s) + spans := t.spans + t.mu.Unlock() + if s.rootSpan { + if wait { + return t.client.upload([]*api.Trace{t.constructTrace(spans)}) + } + go func() { + tr := t.constructTrace(spans) + err := t.client.bundler.Add(tr, 1+len(spans)) + if err == bundler.ErrOversizedItem { + err = t.client.upload([]*api.Trace{tr}) + } + if err != nil { + log.Println("error uploading trace:", err) + } + }() + } + return nil +} + +func (t *trace) constructTrace(spans []*Span) *api.Trace { + apiSpans := make([]*api.TraceSpan, len(spans)) + for i, sp := range spans { + sp.span.StartTime = sp.start.In(time.UTC).Format(time.RFC3339Nano) + sp.span.EndTime = sp.end.In(time.UTC).Format(time.RFC3339Nano) + if t.localOptions&optionStack != 0 { + sp.setStackLabel() + } + sp.SetLabel(labelHost, sp.host) + sp.SetLabel(labelURL, sp.url) + sp.SetLabel(labelMethod, sp.method) + if sp.statusCode != 0 { + sp.SetLabel(labelStatusCode, strconv.Itoa(sp.statusCode)) + } + apiSpans[i] = &sp.span + } + + return &api.Trace{ + ProjectId: t.client.projectID, + TraceId: t.traceID, + Spans: apiSpans, + } +} + +func (c *Client) upload(traces []*api.Trace) error { + _, err := c.service.Projects.PatchTraces(c.projectID, &api.Traces{Traces: traces}).Do() + return err +} + +// Span contains information about one span of a trace. +type Span struct { + trace *trace + + spanMu sync.Mutex // guards span.Labels + span api.TraceSpan + + start time.Time + end time.Time + rootSpan bool + stack [maxStackFrames]uintptr + host string + method string + url string + statusCode int +} + +// Traced reports whether the current span is sampled to be traced. +func (s *Span) Traced() bool { + if s == nil { + return false + } + return s.trace.localOptions&optionTrace != 0 +} + +// NewChild creates a new span with the given name as a child of s. +// If s is nil, does nothing and returns nil. +func (s *Span) NewChild(name string) *Span { + if s == nil { + return nil + } + if !s.Traced() { + // TODO(jbd): Document this behavior in godoc here and elsewhere. + return s + } + return startNewChild(name, s.trace, s.span.SpanId) +} + +// NewRemoteChild creates a new span as a child of s. +// +// Some labels in the span are set from the outgoing *http.Request r. +// +// A header is set in r so that the trace context is propagated to the +// destination. The parent span ID in that header is set as follows: +// - If the request is being traced, then the ID of s is used. +// - If the request is not being traced, but there was a trace context header +// in the incoming request for this trace (the request passed to +// SpanFromRequest), the parent span ID in that header is used. +// - Otherwise, the parent span ID is zero. +// The tracing bit in the options is set if tracing is enabled, or if it was +// set in the incoming request. +// +// If s is nil, does nothing and returns nil. +func (s *Span) NewRemoteChild(r *http.Request) *Span { + if s == nil { + return nil + } + if !s.Traced() { + r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, s.span.ParentSpanId, s.trace.globalOptions)} + return s + } + newSpan := startNewChildWithRequest(r, s.trace, s.span.SpanId) + r.Header[httpHeader] = []string{spanHeader(s.trace.traceID, newSpan.span.SpanId, s.trace.globalOptions)} + return newSpan +} + +// Header returns the value of the X-Cloud-Trace-Context header that +// should be used to propagate the span. This is the inverse of +// SpanFromHeader. +// +// Most users should use NewRemoteChild unless they have specific +// propagation needs or want to control the naming of their span. +// Header() does not create a new span. +func (s *Span) Header() string { + if s == nil { + return "" + } + return spanHeader(s.trace.traceID, s.span.SpanId, s.trace.globalOptions) +} + +func startNewChildWithRequest(r *http.Request, trace *trace, parentSpanID uint64) *Span { + name := r.URL.Host + r.URL.Path // drop scheme and query params + newSpan := startNewChild(name, trace, parentSpanID) + if r.Host == "" { + newSpan.host = r.URL.Host + } else { + newSpan.host = r.Host + } + newSpan.method = r.Method + newSpan.url = r.URL.String() + return newSpan +} + +func startNewChild(name string, trace *trace, parentSpanID uint64) *Span { + spanID := nextSpanID() + for spanID == parentSpanID { + spanID = nextSpanID() + } + newSpan := &Span{ + trace: trace, + span: api.TraceSpan{ + Kind: spanKindClient, + Name: name, + ParentSpanId: parentSpanID, + SpanId: spanID, + }, + start: time.Now(), + } + if trace.localOptions&optionStack != 0 { + _ = runtime.Callers(1, newSpan.stack[:]) + } + return newSpan +} + +// TraceID returns the ID of the trace to which s belongs. +func (s *Span) TraceID() string { + if s == nil { + return "" + } + return s.trace.traceID +} + +// SetLabel sets the label for the given key to the given value. +// If the value is empty, the label for that key is deleted. +// If a label is given a value automatically and by SetLabel, the +// automatically-set value is used. +// If s is nil, does nothing. +// +// SetLabel shouldn't be called after Finish or FinishWait. +func (s *Span) SetLabel(key, value string) { + if s == nil { + return + } + if !s.Traced() { + return + } + s.spanMu.Lock() + defer s.spanMu.Unlock() + + if value == "" { + if s.span.Labels != nil { + delete(s.span.Labels, key) + } + return + } + if s.span.Labels == nil { + s.span.Labels = make(map[string]string) + } + s.span.Labels[key] = value +} + +type FinishOption interface { + modifySpan(s *Span) +} + +type withResponse struct { + *http.Response +} + +// WithResponse returns an option that can be passed to Finish that indicates +// that some labels for the span should be set using the given *http.Response. +func WithResponse(resp *http.Response) FinishOption { + return withResponse{resp} +} +func (u withResponse) modifySpan(s *Span) { + if u.Response != nil { + s.statusCode = u.StatusCode + } +} + +// Finish declares that the span has finished. +// +// If s is nil, Finish does nothing and returns nil. +// +// If the option trace.WithResponse(resp) is passed, then some labels are set +// for s using information in the given *http.Response. This is useful when the +// span is for an outgoing http request; s will typically have been created by +// NewRemoteChild in this case. +// +// If s is a root span (one created by SpanFromRequest) then s, and all its +// descendant spans that have finished, are uploaded to the Google Stackdriver +// Trace server asynchronously. +func (s *Span) Finish(opts ...FinishOption) { + if s == nil { + return + } + if !s.Traced() { + return + } + s.trace.finish(s, false, opts...) +} + +// FinishWait is like Finish, but if s is a root span, it waits until uploading +// is finished, then returns an error if one occurred. +func (s *Span) FinishWait(opts ...FinishOption) error { + if s == nil { + return nil + } + if !s.Traced() { + return nil + } + return s.trace.finish(s, true, opts...) +} + +func spanHeader(traceID string, spanID uint64, options optionFlags) string { + // See https://cloud.google.com/trace/docs/faq for the header format. + return fmt.Sprintf("%s/%d;o=%d", traceID, spanID, options) +} + +func (s *Span) setStackLabel() { + var stack stackLabelValue + lastSigPanic, inTraceLibrary := false, true + for _, pc := range s.stack { + if pc == 0 { + break + } + if !lastSigPanic { + pc-- + } + fn := runtime.FuncForPC(pc) + file, line := fn.FileLine(pc) + // Name has one of the following forms: + // path/to/package.Foo + // path/to/package.(Type).Foo + // For the first form, we store the whole name in the Method field of the + // stack frame. For the second form, we set the Method field to "Foo" and + // the Class field to "path/to/package.(Type)". + name := fn.Name() + if inTraceLibrary && !strings.HasPrefix(name, "cloud.google.com/go/trace.") { + inTraceLibrary = false + } + var class string + if i := strings.Index(name, ")."); i != -1 { + class, name = name[:i+1], name[i+2:] + } + frame := stackFrame{ + Class: class, + Method: name, + Filename: file, + Line: int64(line), + } + if inTraceLibrary && len(stack.Frames) == 1 { + stack.Frames[0] = frame + } else { + stack.Frames = append(stack.Frames, frame) + } + lastSigPanic = fn.Name() == "runtime.sigpanic" + } + if label, err := json.Marshal(stack); err == nil { + s.SetLabel(labelStackTrace, string(label)) + } +} diff --git a/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace_test.go b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace_test.go new file mode 100644 index 000000000..70de65c67 --- /dev/null +++ b/examples/servers/reading-list/vendor/cloud.google.com/go/trace/trace_test.go @@ -0,0 +1,955 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "net/http" + "reflect" + "regexp" + "strings" + "sync" + "testing" + "time" + + "cloud.google.com/go/datastore" + "cloud.google.com/go/internal/testutil" + "cloud.google.com/go/storage" + "golang.org/x/net/context" + api "google.golang.org/api/cloudtrace/v1" + compute "google.golang.org/api/compute/v1" + "google.golang.org/api/iterator" + "google.golang.org/api/option" + dspb "google.golang.org/genproto/googleapis/datastore/v1" + "google.golang.org/grpc" +) + +const testProjectID = "testproject" + +type fakeRoundTripper struct { + reqc chan *http.Request +} + +func newFakeRoundTripper() *fakeRoundTripper { + return &fakeRoundTripper{reqc: make(chan *http.Request)} +} + +func (rt *fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { + rt.reqc <- r + resp := &http.Response{ + Status: "200 OK", + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("{}")), + } + return resp, nil +} + +func newTestClient(rt http.RoundTripper) *Client { + t, err := NewClient(context.Background(), testProjectID, option.WithHTTPClient(&http.Client{Transport: rt})) + if err != nil { + panic(err) + } + return t +} + +type fakeDatastoreServer struct { + dspb.DatastoreServer + fail bool +} + +func (f *fakeDatastoreServer) Lookup(ctx context.Context, req *dspb.LookupRequest) (*dspb.LookupResponse, error) { + if f.fail { + return nil, errors.New("lookup failed") + } + return &dspb.LookupResponse{}, nil +} + +// makeRequests makes some requests. +// span is the root span. rt is the trace client's http client's transport. +// This is used to retrieve the trace uploaded by the client, if any. If +// expectTrace is true, we expect a trace will be uploaded. If synchronous is +// true, the call to Finish is expected not to return before the client has +// uploaded any traces. +func makeRequests(t *testing.T, span *Span, rt *fakeRoundTripper, synchronous bool, expectTrace bool) *http.Request { + ctx := NewContext(context.Background(), span) + tc := newTestClient(&noopTransport{}) + + // An HTTP request. + { + req2, err := http.NewRequest("GET", "http://example.com/bar", nil) + if err != nil { + t.Fatal(err) + } + resp := &http.Response{StatusCode: 200} + s := span.NewRemoteChild(req2) + s.Finish(WithResponse(resp)) + } + + // An autogenerated API call. + { + rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)} + hc := &http.Client{Transport: rt} + computeClient, err := compute.New(hc) + if err != nil { + t.Fatal(err) + } + _, err = computeClient.Zones.List(testProjectID).Context(ctx).Do() + if err != nil { + t.Fatal(err) + } + } + + // A cloud library call that uses the autogenerated API. + { + rt := &fakeRoundTripper{reqc: make(chan *http.Request, 1)} + hc := &http.Client{Transport: rt} + storageClient, err := storage.NewClient(context.Background(), option.WithHTTPClient(hc)) + if err != nil { + t.Fatal(err) + } + var objAttrsList []*storage.ObjectAttrs + it := storageClient.Bucket("testbucket").Objects(ctx, nil) + for { + objAttrs, err := it.Next() + if err != nil && err != iterator.Done { + t.Fatal(err) + } + if err == iterator.Done { + break + } + objAttrsList = append(objAttrsList, objAttrs) + } + } + + // A cloud library call that uses grpc internally. + for _, fail := range []bool{false, true} { + srv, err := testutil.NewServer() + if err != nil { + t.Fatalf("creating test datastore server: %v", err) + } + dspb.RegisterDatastoreServer(srv.Gsrv, &fakeDatastoreServer{fail: fail}) + srv.Start() + conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor())) + if err != nil { + t.Fatalf("connecting to test datastore server: %v", err) + } + datastoreClient, err := datastore.NewClient(ctx, testProjectID, option.WithGRPCConn(conn)) + if err != nil { + t.Fatalf("creating datastore client: %v", err) + } + k := datastore.NameKey("Entity", "stringID", nil) + e := new(datastore.Entity) + datastoreClient.Get(ctx, k, e) + } + + done := make(chan struct{}) + go func() { + if synchronous { + err := span.FinishWait() + if err != nil { + t.Errorf("Unexpected error from span.FinishWait: %v", err) + } + } else { + span.Finish() + } + done <- struct{}{} + }() + if !expectTrace { + <-done + select { + case <-rt.reqc: + t.Errorf("Got a trace, expected none.") + case <-time.After(5 * time.Millisecond): + } + return nil + } else if !synchronous { + <-done + return <-rt.reqc + } else { + select { + case <-done: + t.Errorf("Synchronous Finish didn't wait for trace upload.") + return <-rt.reqc + case <-time.After(5 * time.Millisecond): + r := <-rt.reqc + <-done + return r + } + } +} + +func TestHeader(t *testing.T) { + tests := []struct { + header string + wantTraceID string + wantSpanID uint64 + wantOpts optionFlags + wantOK bool + }{ + { + header: "0123456789ABCDEF0123456789ABCDEF/1;o=1", + wantTraceID: "0123456789ABCDEF0123456789ABCDEF", + wantSpanID: 1, + wantOpts: 1, + wantOK: true, + }, + { + header: "0123456789ABCDEF0123456789ABCDEF/1;o=0", + wantTraceID: "0123456789ABCDEF0123456789ABCDEF", + wantSpanID: 1, + wantOpts: 0, + wantOK: true, + }, + { + header: "0123456789ABCDEF0123456789ABCDEF/1", + wantTraceID: "0123456789ABCDEF0123456789ABCDEF", + wantSpanID: 1, + wantOpts: 0, + wantOK: true, + }, + { + header: "", + wantTraceID: "", + wantSpanID: 0, + wantOpts: 0, + wantOK: false, + }, + } + for _, tt := range tests { + traceID, parentSpanID, opts, _, ok := traceInfoFromHeader(tt.header) + if got, want := traceID, tt.wantTraceID; got != want { + t.Errorf("TraceID(%v) = %q; want %q", tt.header, got, want) + } + if got, want := parentSpanID, tt.wantSpanID; got != want { + t.Errorf("SpanID(%v) = %v; want %v", tt.header, got, want) + } + if got, want := opts, tt.wantOpts; got != want { + t.Errorf("Options(%v) = %v; want %v", tt.header, got, want) + } + if got, want := ok, tt.wantOK; got != want { + t.Errorf("Header exists (%v) = %v; want %v", tt.header, got, want) + } + } +} + +func TestOutgoingReqHeader(t *testing.T) { + all, _ := NewLimitedSampler(1, 1<<16) // trace every request + + tests := []struct { + desc string + traceHeader string + samplingPolicy SamplingPolicy + + wantHeaderRe *regexp.Regexp + }{ + { + desc: "Parent span without sampling options, client samples all", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/1", + samplingPolicy: all, + wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"), + }, + { + desc: "Parent span without sampling options, without client sampling", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/1", + samplingPolicy: nil, + wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"), + }, + { + desc: "Parent span with o=1, client samples none", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=1", + samplingPolicy: nil, + wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=1"), + }, + { + desc: "Parent span with o=0, without client sampling", + traceHeader: "0123456789ABCDEF0123456789ABCDEF/1;o=0", + samplingPolicy: nil, + wantHeaderRe: regexp.MustCompile("0123456789ABCDEF0123456789ABCDEF/\\d+;o=0"), + }, + } + + tc := newTestClient(nil) + for _, tt := range tests { + tc.SetSamplingPolicy(tt.samplingPolicy) + span := tc.SpanFromHeader("/foo", tt.traceHeader) + + req, _ := http.NewRequest("GET", "http://localhost", nil) + span.NewRemoteChild(req) + + if got, re := req.Header.Get(httpHeader), tt.wantHeaderRe; !re.MatchString(got) { + t.Errorf("%v (parent=%q): got header %q; want in format %q", tt.desc, tt.traceHeader, got, re) + } + } +} + +func TestTrace(t *testing.T) { + t.Parallel() + testTrace(t, false, true) +} + +func TestTraceWithWait(t *testing.T) { + testTrace(t, true, true) +} + +func TestTraceFromHeader(t *testing.T) { + t.Parallel() + testTrace(t, false, false) +} + +func TestTraceFromHeaderWithWait(t *testing.T) { + testTrace(t, false, true) +} + +func TestNewSpan(t *testing.T) { + const traceID = "0123456789ABCDEF0123456789ABCDEF" + + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + span := traceClient.NewSpan("/foo") + span.trace.traceID = traceID + + uploaded := makeRequests(t, span, rt, true, true) + + if uploaded == nil { + t.Fatalf("No trace uploaded, expected one.") + } + + expected := api.Traces{ + Traces: []*api.Trace{ + { + ProjectId: testProjectID, + Spans: []*api.TraceSpan{ + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "example.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "http://example.com/bar", + }, + Name: "example.com/bar", + }, + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "www.googleapis.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones", + }, + Name: "www.googleapis.com/compute/v1/projects/testproject/zones", + }, + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "www.googleapis.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o", + }, + Name: "www.googleapis.com/storage/v1/b/testbucket/o", + }, + &api.TraceSpan{ + Kind: "RPC_CLIENT", + Labels: nil, + Name: "/google.datastore.v1.Datastore/Lookup", + }, + &api.TraceSpan{ + Kind: "RPC_CLIENT", + Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"}, + Name: "/google.datastore.v1.Datastore/Lookup", + }, + { + Kind: "SPAN_KIND_UNSPECIFIED", + Labels: map[string]string{}, + Name: "/foo", + }, + }, + TraceId: traceID, + }, + }, + } + + body, err := ioutil.ReadAll(uploaded.Body) + if err != nil { + t.Fatal(err) + } + var patch api.Traces + err = json.Unmarshal(body, &patch) + if err != nil { + t.Fatal(err) + } + + if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) { + got, _ := json.Marshal(patch) + want, _ := json.Marshal(expected) + t.Fatalf("PatchTraces request: got %s want %s", got, want) + } + + n := len(patch.Traces[0].Spans) + rootSpan := patch.Traces[0].Spans[n-1] + for i, s := range patch.Traces[0].Spans { + if a, b := s.StartTime, s.EndTime; a > b { + t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b) + } + if a, b := rootSpan.StartTime, s.StartTime; a > b { + t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b) + } + if a, b := s.EndTime, rootSpan.EndTime; a > b { + t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b) + } + if i > 1 && i < n-1 { + if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b { + t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b) + } + } + } + + if x := rootSpan.ParentSpanId; x != 0 { + t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 0) + } + for i, s := range patch.Traces[0].Spans { + if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y { + t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x) + } + } + for i, s := range patch.Traces[0].Spans { + s.EndTime = "" + labels := &expected.Traces[0].Spans[i].Labels + for key, value := range *labels { + if v, ok := s.Labels[key]; !ok { + t.Errorf("Span %d is missing Label %q:%q", i, key, value) + } else if key == "trace.cloud.google.com/http/url" { + if !strings.HasPrefix(v, value) { + t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value) + } + } else if v != value { + t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value) + } + } + for key := range s.Labels { + if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok { + t.Errorf("Span %d: unexpected label %q", i, key) + } + } + *labels = nil + s.Labels = nil + s.ParentSpanId = 0 + if s.SpanId == 0 { + t.Errorf("Incorrect SpanId: got 0 want nonzero") + } + s.SpanId = 0 + s.StartTime = "" + } + if !reflect.DeepEqual(patch, expected) { + got, _ := json.Marshal(patch) + want, _ := json.Marshal(expected) + t.Errorf("PatchTraces request: got %s want %s", got, want) + } +} + +func testTrace(t *testing.T, synchronous bool, fromRequest bool) { + const header = `0123456789ABCDEF0123456789ABCDEF/42;o=3` + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + + span := traceClient.SpanFromHeader("/foo", header) + headerOrReqLabels := map[string]string{} + headerOrReqName := "/foo" + + if fromRequest { + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if err != nil { + t.Fatal(err) + } + req.Header.Set("X-Cloud-Trace-Context", header) + span = traceClient.SpanFromRequest(req) + headerOrReqLabels = map[string]string{ + "trace.cloud.google.com/http/host": "example.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/url": "http://example.com/foo", + } + headerOrReqName = "example.com/foo" + } + + uploaded := makeRequests(t, span, rt, synchronous, true) + if uploaded == nil { + t.Fatalf("No trace uploaded, expected one.") + } + + expected := api.Traces{ + Traces: []*api.Trace{ + { + ProjectId: testProjectID, + Spans: []*api.TraceSpan{ + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "example.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "http://example.com/bar", + }, + Name: "example.com/bar", + }, + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "www.googleapis.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "https://www.googleapis.com/compute/v1/projects/testproject/zones", + }, + Name: "www.googleapis.com/compute/v1/projects/testproject/zones", + }, + { + Kind: "RPC_CLIENT", + Labels: map[string]string{ + "trace.cloud.google.com/http/host": "www.googleapis.com", + "trace.cloud.google.com/http/method": "GET", + "trace.cloud.google.com/http/status_code": "200", + "trace.cloud.google.com/http/url": "https://www.googleapis.com/storage/v1/b/testbucket/o", + }, + Name: "www.googleapis.com/storage/v1/b/testbucket/o", + }, + &api.TraceSpan{ + Kind: "RPC_CLIENT", + Labels: nil, + Name: "/google.datastore.v1.Datastore/Lookup", + }, + &api.TraceSpan{ + Kind: "RPC_CLIENT", + Labels: map[string]string{"error": "rpc error: code = Unknown desc = lookup failed"}, + Name: "/google.datastore.v1.Datastore/Lookup", + }, + { + Kind: "RPC_SERVER", + Labels: headerOrReqLabels, + Name: headerOrReqName, + }, + }, + TraceId: "0123456789ABCDEF0123456789ABCDEF", + }, + }, + } + + body, err := ioutil.ReadAll(uploaded.Body) + if err != nil { + t.Fatal(err) + } + var patch api.Traces + err = json.Unmarshal(body, &patch) + if err != nil { + t.Fatal(err) + } + + if len(patch.Traces) != len(expected.Traces) || len(patch.Traces[0].Spans) != len(expected.Traces[0].Spans) { + got, _ := json.Marshal(patch) + want, _ := json.Marshal(expected) + t.Fatalf("PatchTraces request: got %s want %s", got, want) + } + + n := len(patch.Traces[0].Spans) + rootSpan := patch.Traces[0].Spans[n-1] + for i, s := range patch.Traces[0].Spans { + if a, b := s.StartTime, s.EndTime; a > b { + t.Errorf("span %d start time is later than its end time (%q, %q)", i, a, b) + } + if a, b := rootSpan.StartTime, s.StartTime; a > b { + t.Errorf("trace start time is later than span %d start time (%q, %q)", i, a, b) + } + if a, b := s.EndTime, rootSpan.EndTime; a > b { + t.Errorf("span %d end time is later than trace end time (%q, %q)", i, a, b) + } + if i > 1 && i < n-1 { + if a, b := patch.Traces[0].Spans[i-1].EndTime, s.StartTime; a > b { + t.Errorf("span %d end time is later than span %d start time (%q, %q)", i-1, i, a, b) + } + } + } + + if x := rootSpan.ParentSpanId; x != 42 { + t.Errorf("Incorrect ParentSpanId: got %d want %d", x, 42) + } + for i, s := range patch.Traces[0].Spans { + if x, y := rootSpan.SpanId, s.ParentSpanId; i < n-1 && x != y { + t.Errorf("Incorrect ParentSpanId in span %d: got %d want %d", i, y, x) + } + } + for i, s := range patch.Traces[0].Spans { + s.EndTime = "" + labels := &expected.Traces[0].Spans[i].Labels + for key, value := range *labels { + if v, ok := s.Labels[key]; !ok { + t.Errorf("Span %d is missing Label %q:%q", i, key, value) + } else if key == "trace.cloud.google.com/http/url" { + if !strings.HasPrefix(v, value) { + t.Errorf("Span %d Label %q: got value %q want prefix %q", i, key, v, value) + } + } else if v != value { + t.Errorf("Span %d Label %q: got value %q want %q", i, key, v, value) + } + } + for key := range s.Labels { + if _, ok := (*labels)[key]; key != "trace.cloud.google.com/stacktrace" && !ok { + t.Errorf("Span %d: unexpected label %q", i, key) + } + } + *labels = nil + s.Labels = nil + s.ParentSpanId = 0 + if s.SpanId == 0 { + t.Errorf("Incorrect SpanId: got 0 want nonzero") + } + s.SpanId = 0 + s.StartTime = "" + } + if !reflect.DeepEqual(patch, expected) { + got, _ := json.Marshal(patch) + want, _ := json.Marshal(expected) + t.Errorf("PatchTraces request: got %s \n\n want %s", got, want) + } +} + +func TestNoTrace(t *testing.T) { + testNoTrace(t, false, true) +} + +func TestNoTraceWithWait(t *testing.T) { + testNoTrace(t, true, true) +} + +func TestNoTraceFromHeader(t *testing.T) { + testNoTrace(t, false, false) +} + +func TestNoTraceFromHeaderWithWait(t *testing.T) { + testNoTrace(t, true, false) +} + +func testNoTrace(t *testing.T, synchronous bool, fromRequest bool) { + for _, header := range []string{ + `0123456789ABCDEF0123456789ABCDEF/42;o=2`, + `0123456789ABCDEF0123456789ABCDEF/42;o=0`, + `0123456789ABCDEF0123456789ABCDEF/42`, + `0123456789ABCDEF0123456789ABCDEF`, + ``, + } { + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + var span *Span + if fromRequest { + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if header != "" { + req.Header.Set("X-Cloud-Trace-Context", header) + } + if err != nil { + t.Fatal(err) + } + span = traceClient.SpanFromRequest(req) + } else { + span = traceClient.SpanFromHeader("/foo", header) + } + uploaded := makeRequests(t, span, rt, synchronous, false) + if uploaded != nil { + t.Errorf("Got a trace, expected none.") + } + } +} + +func TestSample(t *testing.T) { + // A deterministic test of the sampler logic. + type testCase struct { + rate float64 + maxqps float64 + want int + } + const delta = 25 * time.Millisecond + for _, test := range []testCase{ + // qps won't matter, so we will sample half of the 79 calls + {0.50, 100, 40}, + // with 1 qps and a burst of 2, we will sample twice in second #1, once in the partial second #2 + {0.50, 1, 3}, + } { + sp, err := NewLimitedSampler(test.rate, test.maxqps) + if err != nil { + t.Fatal(err) + } + s := sp.(*sampler) + sampled := 0 + tm := time.Now() + for i := 0; i < 80; i++ { + if s.sample(Parameters{}, tm, float64(i%2)).Sample { + sampled++ + } + tm = tm.Add(delta) + } + if sampled != test.want { + t.Errorf("rate=%f, maxqps=%f: got %d samples, want %d", test.rate, test.maxqps, sampled, test.want) + } + } +} + +func TestSampling(t *testing.T) { + t.Parallel() + // This scope tests sampling in a larger context, with real time and randomness. + wg := sync.WaitGroup{} + type testCase struct { + rate float64 + maxqps float64 + expectedRange [2]int + } + for _, test := range []testCase{ + {0, 5, [2]int{0, 0}}, + {5, 0, [2]int{0, 0}}, + {0.50, 100, [2]int{20, 60}}, + {0.50, 1, [2]int{3, 4}}, // Windows, with its less precise clock, sometimes gives 4. + } { + wg.Add(1) + go func(test testCase) { + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + traceClient.bundler.BundleByteLimit = 1 + p, err := NewLimitedSampler(test.rate, test.maxqps) + if err != nil { + t.Fatalf("NewLimitedSampler: %v", err) + } + traceClient.SetSamplingPolicy(p) + ticker := time.NewTicker(25 * time.Millisecond) + sampled := 0 + for i := 0; i < 79; i++ { + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if err != nil { + t.Fatal(err) + } + span := traceClient.SpanFromRequest(req) + span.Finish() + select { + case <-rt.reqc: + <-ticker.C + sampled++ + case <-ticker.C: + } + } + ticker.Stop() + if test.expectedRange[0] > sampled || sampled > test.expectedRange[1] { + t.Errorf("rate=%f, maxqps=%f: got %d samples want ∈ %v", test.rate, test.maxqps, sampled, test.expectedRange) + } + wg.Done() + }(test) + } + wg.Wait() +} + +func TestBundling(t *testing.T) { + t.Parallel() + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + traceClient.bundler.DelayThreshold = time.Second / 2 + traceClient.bundler.BundleCountThreshold = 10 + p, err := NewLimitedSampler(1, 99) // sample every request. + if err != nil { + t.Fatalf("NewLimitedSampler: %v", err) + } + traceClient.SetSamplingPolicy(p) + + for i := 0; i < 35; i++ { + go func() { + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if err != nil { + t.Fatal(err) + } + span := traceClient.SpanFromRequest(req) + span.Finish() + }() + } + + // Read the first three bundles. + <-rt.reqc + <-rt.reqc + <-rt.reqc + + // Test that the fourth bundle isn't sent early. + select { + case <-rt.reqc: + t.Errorf("bundle sent too early") + case <-time.After(time.Second / 4): + <-rt.reqc + } + + // Test that there aren't extra bundles. + select { + case <-rt.reqc: + t.Errorf("too many bundles sent") + case <-time.After(time.Second): + } +} + +func TestWeights(t *testing.T) { + const ( + expectedNumTraced = 10100 + numTracedEpsilon = 100 + expectedTotalWeight = 50000 + totalWeightEpsilon = 5000 + ) + rng := rand.New(rand.NewSource(1)) + const delta = 2 * time.Millisecond + for _, headerRate := range []float64{0.0, 0.5, 1.0} { + // Simulate 10 seconds of requests arriving at 500qps. + // + // The sampling policy tries to sample 25% of them, but has a qps limit of + // 100, so it will not be able to. The returned weight should be higher + // for some sampled requests to compensate. + // + // headerRate is the fraction of incoming requests that have a trace header + // set. The qps limit should not be exceeded, even if headerRate is high. + sp, err := NewLimitedSampler(0.25, 100) + if err != nil { + t.Fatal(err) + } + s := sp.(*sampler) + tm := time.Now() + totalWeight := 0.0 + numTraced := 0 + seenLargeWeight := false + for i := 0; i < 50000; i++ { + d := s.sample(Parameters{HasTraceHeader: rng.Float64() < headerRate}, tm, rng.Float64()) + if d.Trace { + numTraced++ + } + if d.Sample { + totalWeight += d.Weight + if x := int(d.Weight) / 4; x <= 0 || x >= 100 || d.Weight != float64(x)*4.0 { + t.Errorf("weight: got %f, want a small positive multiple of 4", d.Weight) + } + if d.Weight > 4 { + seenLargeWeight = true + } + } + tm = tm.Add(delta) + } + if !seenLargeWeight { + t.Errorf("headerRate %f: never saw sample weight higher than 4.", headerRate) + } + if numTraced < expectedNumTraced-numTracedEpsilon || expectedNumTraced+numTracedEpsilon < numTraced { + t.Errorf("headerRate %f: got %d traced requests, want ∈ [%d, %d]", headerRate, numTraced, expectedNumTraced-numTracedEpsilon, expectedNumTraced+numTracedEpsilon) + } + if totalWeight < expectedTotalWeight-totalWeightEpsilon || expectedTotalWeight+totalWeightEpsilon < totalWeight { + t.Errorf("headerRate %f: got total weight %f want ∈ [%d, %d]", headerRate, totalWeight, expectedTotalWeight-totalWeightEpsilon, expectedTotalWeight+totalWeightEpsilon) + } + } +} + +type alwaysTrace struct{} + +func (a alwaysTrace) Sample(p Parameters) Decision { + return Decision{Trace: true} +} + +type neverTrace struct{} + +func (a neverTrace) Sample(p Parameters) Decision { + return Decision{Trace: false} +} + +func TestPropagation(t *testing.T) { + rt := newFakeRoundTripper() + traceClient := newTestClient(rt) + for _, header := range []string{ + `0123456789ABCDEF0123456789ABCDEF/42;o=0`, + `0123456789ABCDEF0123456789ABCDEF/42;o=1`, + `0123456789ABCDEF0123456789ABCDEF/42;o=2`, + `0123456789ABCDEF0123456789ABCDEF/42;o=3`, + `0123456789ABCDEF0123456789ABCDEF/0;o=0`, + `0123456789ABCDEF0123456789ABCDEF/0;o=1`, + `0123456789ABCDEF0123456789ABCDEF/0;o=2`, + `0123456789ABCDEF0123456789ABCDEF/0;o=3`, + ``, + } { + for _, policy := range []SamplingPolicy{ + nil, + alwaysTrace{}, + neverTrace{}, + } { + traceClient.SetSamplingPolicy(policy) + req, err := http.NewRequest("GET", "http://example.com/foo", nil) + if err != nil { + t.Fatal(err) + } + if header != "" { + req.Header.Set("X-Cloud-Trace-Context", header) + } + + span := traceClient.SpanFromRequest(req) + + req2, err := http.NewRequest("GET", "http://example.com/bar", nil) + if err != nil { + t.Fatal(err) + } + req3, err := http.NewRequest("GET", "http://example.com/baz", nil) + if err != nil { + t.Fatal(err) + } + span.NewRemoteChild(req2) + span.NewRemoteChild(req3) + + var ( + t1, t2, t3 string + s1, s2, s3 uint64 + o1, o2, o3 uint64 + ) + fmt.Sscanf(header, "%32s/%d;o=%d", &t1, &s1, &o1) + fmt.Sscanf(req2.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t2, &s2, &o2) + fmt.Sscanf(req3.Header.Get("X-Cloud-Trace-Context"), "%32s/%d;o=%d", &t3, &s3, &o3) + + if header == "" { + if t2 != t3 { + t.Errorf("expected the same trace ID in child requests, got %q %q", t2, t3) + } + } else { + if t2 != t1 || t3 != t1 { + t.Errorf("trace IDs should be passed to child requests") + } + } + trace := policy == alwaysTrace{} || policy == nil && (o1&1) != 0 + if header == "" { + if trace && (s2 == 0 || s3 == 0) { + t.Errorf("got span IDs %d %d in child requests, want nonzero", s2, s3) + } + if trace && s2 == s3 { + t.Errorf("got span IDs %d %d in child requests, should be different", s2, s3) + } + if !trace && (s2 != 0 || s3 != 0) { + t.Errorf("got span IDs %d %d in child requests, want zero", s2, s3) + } + } else { + if trace && (s2 == s1 || s3 == s1 || s2 == s3) { + t.Errorf("parent span IDs in input and outputs should be all different, got %d %d %d", s1, s2, s3) + } + if !trace && (s2 != s1 || s3 != s1) { + t.Errorf("parent span ID in input, %d, should have been equal to parent span IDs in output: %d %d", s1, s2, s3) + } + } + expectTraceOption := policy == alwaysTrace{} || (o1&1) != 0 + if expectTraceOption != ((o2&1) != 0) || expectTraceOption != ((o3&1) != 0) { + t.Errorf("tracing flag in child requests should be %t, got options %d %d", expectTraceOption, o2, o3) + } + } + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.gitignore b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.gitignore new file mode 100644 index 000000000..12afa21ae --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.gitignore @@ -0,0 +1,2 @@ +*.swp +.cover diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.travis.yml b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.travis.yml new file mode 100644 index 000000000..0ca585429 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/.travis.yml @@ -0,0 +1,21 @@ +language: go + +before_install: + # Decrypts a script that installs an authenticated cookie + # for git to use when cloning from googlesource.com. + # Bypasses "bandwidth limit exceeded" errors. + # See github.com/golang/go/issues/12933 + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then openssl aes-256-cbc -K $encrypted_83935ad6c1b7_key -iv $encrypted_83935ad6c1b7_iv -in gitcookies.sh.enc -out gitcookies.sh -d; fi + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover + +install: + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash gitcookies.sh; fi + +script: + - make test + - make coverage + +go: + - 1.8.x diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CODE_OF_CONDUCT.md b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..a0a50ac9b --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +--- +layout: code-of-conduct +version: v1.0 +--- + +This code of conduct outlines our expectations for participants within the **Gizmo** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. + +Our open source community strives to: + +* **Be friendly and patient.** +* **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. +* **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. +* **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. +* **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. +* **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. + +## Definitions + +Harassment includes, but is not limited to: + +- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation +- Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment +- Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle +- Physical contact and simulated physical contact (eg, textual descriptions like “*hug*†or “*backrub*â€) without consent or after a request to stop +- Threats of violence, both physical and psychological +- Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm +- Deliberate intimidation +- Stalking or following +- Harassing photography or recording, including logging online activity for harassment purposes +- Sustained disruption of discussion +- Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour +- Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others +- Continued one-on-one communication after requests to cease +- Deliberate “outing†of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse +- Publication of non-harassing private communication + +Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding: + +- ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’ +- Reasonable communication of boundaries, such as “leave me alone,†“go away,†or “I’m not discussing this with you†+- Refusal to explain or debate social justice concepts +- Communicating in a ‘tone’ you don’t find congenial +- Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions + + +### Diversity Statement + +We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. + +Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected +characteristics above, including participants with disabilities. + +### Reporting Issues + +If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **code@nytimes.com**. All reports will be handled with discretion. In your report please include: + +- Your contact information. +- Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please +include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. +- Any additional information that may be helpful. + +After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse. + +### Attribution & Acknowledgements + +We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration: + +* [Django](https://www.djangoproject.com/conduct/reporting/) +* [Python](https://www.python.org/community/diversity/) +* [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) +* [Contributor Covenant](http://contributor-covenant.org/) +* [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/) +* [Citizen Code of Conduct](http://citizencodeofconduct.org/) + +This Code of Conduct was based on https://github.com/todogroup/opencodeofconduct diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CONTRIBUTING.md b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CONTRIBUTING.md new file mode 100644 index 000000000..ecc160b7f --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing to Gizmo + +Gizmo is an open source project started by handful of developers at The New York Times and open to the entire Go community. + +We really appreciate your help! + +## Filing issues + +When filing an issue, make sure to answer these five questions: + +1. What version of Go are you using (`go version`)? +2. What operating system and processor architecture are you using? +3. What did you do? +4. What did you expect to see? +5. What did you see instead? + +General questions should go to the [#gizmo](https://gophers.slack.com/messages/gizmo/) channel in the [Gopher Slack community](https://blog.gopheracademy.com/gophers-slack-community/) instead of the issue tracker. The gophers there will answer or ask you to file an issue if you've tripped over a bug. + +## Contributing code + +Before submitting changes, please follow these guidelines: + +1. Check the open issues and pull requests for existing discussions. +2. Open an issue to discuss a new feature. +3. Write tests. +4. Make sure code follows the ['Go Code Review Comments'](https://github.com/golang/go/wiki/CodeReviewComments). +5. Make sure your changes pass `make test`. +6. Make sure the entire test suite passes locally and on Travis CI. +7. Open a Pull Request. +8. [Squash your commits](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) after receiving feedback and add a [great commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +Unless otherwise noted, the Gizmo source files are distributed under +the Apache 2.0-style license found in the LICENSE.md file. + +[Please review our Code of Conduct](https://github.com/NYTimes/gizmo/blob/master/CODE_OF_CONDUCT.md) diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/LICENSE.md b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/LICENSE.md new file mode 100644 index 000000000..3de17ba94 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/LICENSE.md @@ -0,0 +1,13 @@ +Copyright (c) 2016 The New York Times Company + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this library except in compliance with the License. +You may obtain a copy of the License at + + [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/Makefile b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/Makefile new file mode 100644 index 000000000..22005823e --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/Makefile @@ -0,0 +1,62 @@ +all: test + +deps: + go get -d -v github.com/NYTimes/gizmo/... + +updatedeps: + go get -d -v -u -f github.com/NYTimes/gizmo/... + +testdeps: + go get -d -v -t github.com/NYTimes/gizmo/... + +updatetestdeps: + go get -d -v -t -u -f github.com/NYTimes/gizmo/... + +build: deps + go build github.com/NYTimes/gizmo/... + +install: deps + go install github.com/NYTimes/gizmo/... + +lint: testdeps + go get -v github.com/golang/lint/golint + for file in $$(find . -name '*.go' | grep -v '\.pb\.go\|\.pb\.gw\.go\|examples\|pubsub\/aws\/awssub_test\.go' | grep -v 'server\/kit\/kitserver_pb_test\.go'); do \ + golint $${file}; \ + if [ -n "$$(golint $${file})" ]; then \ + exit 1; \ + fi; \ + done + +vet: testdeps + go vet github.com/NYTimes/gizmo/... + +errcheck: testdeps + go get -v github.com/kisielk/errcheck + errcheck -ignoretests github.com/NYTimes/gizmo/... + +pretest: lint vet # errcheck + +test: testdeps pretest + go test github.com/NYTimes/gizmo/... + +clean: + go clean -i github.com/NYTimes/gizmo/... + +coverage: testdeps + ./coverage.sh --coveralls + +.PHONY: \ + all \ + deps \ + updatedeps \ + testdeps \ + updatetestdeps \ + build \ + install \ + lint \ + vet \ + errcheck \ + pretest \ + test \ + clean \ + coverage diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/README.md b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/README.md new file mode 100644 index 000000000..337ef7f67 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/README.md @@ -0,0 +1,252 @@ +# Gizmo Microservice Toolkit [![GoDoc](https://godoc.org/github.com/gizmo/gizmo?status.svg)](https://godoc.org/github.com/NYTimes/gizmo) [![Build Status](https://travis-ci.org/NYTimes/gizmo.svg?branch=master)](https://travis-ci.org/NYTimes/gizmo) [![Coverage Status](https://coveralls.io/repos/NYTimes/gizmo/badge.svg?branch=master&service=github)](https://coveralls.io/github/NYTimes/gizmo?branch=master) + +![Gizmo!](http://graphics8.nytimes.com/images/blogs/open/2015/gizmo.png) + +This toolkit provides packages to put together server and pubsub daemons with the following features: + +* standardized configuration and logging +* health check endpoints with configurable strategies +* configuration for managing pprof endpoints and log levels +* structured logging containing basic request information +* useful metrics for endpoints +* graceful shutdowns +* basic interfaces to define our expectations and vocabulary + +In this toolkit, you will find: + +## The `config` packages + +The `config` package contains a handful of useful functions to load to configuration structs from JSON files, JSON blobs in Consul k/v or environment variables. + +The subpackages contain structs meant for managing common configuration options and credentials. There are currently configs for: + +* Go Kit Metrics +* MySQL +* MongoDB +* Oracle +* PostgreSQL +* AWS (S3, DynamoDB, ElastiCache) +* GCP +* Gorilla's `securecookie` + +The package also has a generic `Config` type in the `config/combined` subpackage that contains all of the above types. It's meant to be a 'catch all' convenience struct that many applications should be able to use. + +## The `server` package + +This package is the bulk of the toolkit and relies on `server.Config` for any managing `Server` implementations. A server must implement the following interface: + +```go +// Server is the basic interface that defines what expect from any server. +type Server interface { + Register(Service) error + Start() error + Stop() error +} +``` + +The package offers 2 server implementations: + +`SimpleServer`, which is capable of handling basic HTTP and JSON requests via 5 of the available `Service` implementations: `SimpleService`, `JSONService`, `ContextService`, `MixedService` and a `MixedContextService`. A service and these implementations will be defined below. + +`RPCServer`, which is capable of serving a gRPC server on one port and JSON endpoints on another. This kind of server can only handle the `RPCService` implementation. + +The `Service` interface is minimal to allow for maximum flexibility: +```go +type Service interface { + Prefix() string + + // Middleware provides a hook for service-wide middleware. + Middleware(http.Handler) http.Handler +} +``` + +The 5 service types that are accepted and hostable on the `SimpleServer`: + +```go +type SimpleService interface { + Service + + // router - method - func + Endpoints() map[string]map[string]http.HandlerFunc +} + +type JSONService interface { + Service + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint +} + +type MixedService interface { + Service + + // route - method - func + Endpoints() map[string]map[string]http.HandlerFunc + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint +} + +type ContextService interface { + Service + + // route - method - func + ContextEndpoints() map[string]map[string]ContextHandlerFunc + // ContextMiddleware provides a hook for service-wide middleware around ContextHandler + ContextMiddleware(ContextHandler) ContextHandler +} + +type MixedContextService interface { + ContextService + + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + JSONContextMiddleware(JSONContextEndpoint) JSONContextEndpoint +} +``` + +Where `JSONEndpoint`, `JSONContextEndpoint`, `ContextHandler` and `ContextHandlerFunc` are defined as: + +```go +type JSONEndpoint func(*http.Request) (int, interface{}, error) + +type JSONContextEndpoint func(context.Context, *http.Request) (int, interface{}, error) + +type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) +} + +type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request) +``` + +Also, the one service type that works with an `RPCServer`: + +```go +type RPCService interface { + ContextService + + Service() (grpc.ServiceDesc, interface{}) + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONContextEndpoints. + JSONMiddlware(JSONContextEndpoint) JSONContextEndpoint +} +``` + +The `Middleware(..)` functions offer each service a 'hook' to wrap each of its endpoints. This may be handy for adding additional headers or context to the request. This is also the point where other, third-party middleware could be easily plugged in (i.e. oauth, tracing, metrics, logging, etc.) + +## The `pubsub` packages + +The base `pubsub` package contains three generic interfaces for publishing data to queues and subscribing and consuming data from those queues. + +```go +// Publisher is a generic interface to encapsulate how we want our publishers +// to behave. Until we find reason to change, we're forcing all publishers +// to emit protobufs. +type Publisher interface { + // Publish will publish a message. + Publish(ctx context.Context, key string, msg proto.Message) error + // Publish will publish a []byte message. + PublishRaw(ctx context.Context, key string, msg []byte) error +} + +// MultiPublisher is an interface for publishers who support sending multiple +// messages in a single request, in addition to individual messages. +type MultiPublisher interface { + Publisher + + // PublishMulti will publish multiple messages with a context. + PublishMulti(context.Context, []string, []proto.Message) error + // PublishMultiRaw will publish multiple raw byte array messages with a context. + PublishMultiRaw(context.Context, []string, [][]byte) error +} + +// Subscriber is a generic interface to encapsulate how we want our subscribers +// to behave. For now the system will auto stop if it encounters any errors. If +// a user encounters a closed channel, they should check the Err() method to see +// what happened. +type Subscriber interface { + // Start will return a channel of raw messages. + Start() <-chan SubscriberMessage + // Err will contain any errors returned from the consumer connection. + Err() error + // Stop will initiate a graceful shutdown of the subscriber connection. + Stop() error +} +``` + +Where a `SubscriberMessage` is an interface that gives implementations a hook for acknowledging/delete messages. Take a look at the docs for each implementation in `pubsub` to see how they behave. + +There are currently major 4 implementations of the `pubsub` interfaces: + +For pubsub via Amazon's SNS/SQS, you can use the `pubsub/aws` package. + +For pubsub via Google's Pubsub, you can use the `pubsub/gcp` package. This package offers 2 ways of publishing to Google PubSub. `gcp.NewPublisher` uses the RPC client and `gcp.NewHTTPPublisher` will publish over plain HTTP, which is useful for the App Engine standard environment. + +For pubsub via Kafka topics, you can use the `pubsub/kafka` package. + +For publishing via HTTP, you can use the `pubsub/http` package. + +The `MultiPublisher` interface is only implemented by `pubsub/gcp`. + +## The `pubsub/pubsubtest` package + +This package contains 'test' implementations of the `pubsub.Publisher`, `pubsub.MultiPublisher`, and `pubsub.Subscriber` interfaces that will allow developers to easily mock out and test their `pubsub` implementations: + +```go +type TestPublisher struct { + // Published will contain a list of all messages that have been published. + Published []TestPublishMsg + + // GivenError will be returned by the TestPublisher on publish. + // Good for testing error scenarios. + GivenError error + + // FoundError will contain any errors encountered while marshalling + // the protobuf struct. + FoundError error +} + +type TestSubscriber struct { + // ProtoMessages will be marshalled into []byte used to mock out + // a feed if it is populated. + ProtoMessages []proto.Message + + // JSONMEssages will be marshalled into []byte and used to mock out + // a feed if it is populated. + JSONMessages []interface{} + + // GivenErrError will be returned by the TestSubscriber on Err(). + // Good for testing error scenarios. + GivenErrError error + + // GivenStopError will be returned by the TestSubscriber on Stop(). + // Good for testing error scenarios. + GivenStopError error + + // FoundError will contain any errors encountered while marshalling + // the JSON and protobuf struct. + FoundError error +} +``` + +## The `web` package + +This package contains a handful of very useful functions for parsing types from request queries and payloads. + +## Examples + +* Several reference implementations utilizing `server` and `pubsub` are available in the ['examples'](https://github.com/NYTimes/gizmo/tree/master/examples) subdirectory. + +The Gizmo logo was based on the Go mascot designed by Renée French and copyrighted under the Creative Commons Attribution 3.0 license. diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/config.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/config.go new file mode 100644 index 000000000..e5a506d90 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/config.go @@ -0,0 +1,71 @@ +package config + +import ( + "encoding/json" + "io/ioutil" + "log" + "strings" + + "github.com/hashicorp/consul/api" + "github.com/kelseyhightower/envconfig" +) + +// EnvAppName is used as a prefix for environment variable +// names when using the LoadXFromEnv funcs. +// It defaults to empty. +var EnvAppName = "" + +// LoadEnvConfig will use envconfig to load the +// given config struct from the environment. +func LoadEnvConfig(c interface{}) { + err := envconfig.Process(EnvAppName, c) + if err != nil { + log.Fatalf("unable to load env variable for %T: %s", c, err) + } +} + +// LoadJSONFile is a helper function to read a config file into whatever +// config struct you need. For example, your custom config could be composed +// of one or more of the given Config, AWS, MySQL, Oracle or MongoDB structs. +func LoadJSONFile(fileName string, cfg interface{}) { + cb, err := ioutil.ReadFile(fileName) + if err != nil { + log.Fatalf("Unable to read config file '%s': %s", fileName, err) + } + + if err = json.Unmarshal(cb, &cfg); err != nil { + log.Fatalf("Unable to parse JSON in config file '%s': %s", fileName, err) + } +} + +// LoadJSONFromConsulKV is a helper function to read a JSON string found +// in a path defined by configKey inside Consul's Key Value storage then +// unmarshalled into a config struct, like LoadJSONFile does. +// It assumes that the Consul agent is running with the default setup, +// where the HTTP API is found via 127.0.0.1:8500. +func LoadJSONFromConsulKV(configKeyParameter string, cfg interface{}) interface{} { + configKeyParameterValue := strings.SplitN(configKeyParameter, ":", 2) + if len(configKeyParameterValue) < 2 { + log.Fatalf("Undefined Consul KV configuration path. It should be defined using the format consul:path/to/JSON/string") + } + configKey := configKeyParameterValue[1] + client, err := api.NewClient(api.DefaultConfig()) + if err != nil { + log.Fatalf("Unable to setup Consul client: %s", err) + } + kv := client.KV() + kvPair, _, err := kv.Get(configKey, nil) + if err != nil { + log.Fatalf("Unable to read config in key '%s' from Consul KV: %s", configKey, err) + } + if kvPair == nil { + log.Fatalf("Undefined key '%s' in Consul KV", configKey) + } + if len(kvPair.Value) == 0 { + log.Fatalf("Empty JSON in Consul KV for key '%s'", configKey) + } + if err = json.Unmarshal(kvPair.Value, &cfg); err != nil { + log.Fatalf("Unable to parse JSON in Consul KV for key '%s': %s", configKey, err) + } + return cfg +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/doc.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/doc.go new file mode 100644 index 000000000..3b1815bb4 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/doc.go @@ -0,0 +1,17 @@ +/* +Package config contains a handful of useful functions to load to configuration structs from JSON files, JSON blobs in Consul k/v or environment variables. + +The subpackages contain structs meant for managing common configuration options and credentials. There are currently configs for: + +* Go Kit Metrics +* MySQL +* MongoDB +* Oracle +* PostgreSQL +* AWS (S3, DynamoDB, ElastiCache) +* GCP +* Gorilla's `securecookie` + +The package also has a generic `Config` type in the `config/combined` package that contains all of the above types. It's meant to be a 'catch all' convenience struct that many applications should be able to use. +*/ +package config diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/flags.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/flags.go new file mode 100644 index 000000000..a5b563c70 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/flags.go @@ -0,0 +1,41 @@ +package config + +import "flag" + +// DefaultConfigLocation is the default filepath for JSON config files. +const DefaultConfigLocation = "/opt/nyt/etc/conf.json" + +// SetLogOverride will check `*LogCLI` for any values +// and override the given string pointer if LogCLI is set. +// If LogCLI is set to "dev", the given log var will be set to "". +func SetLogOverride(log *string) { + // LogCLI is a pointer to the value of the '-log' command line flag. It is meant to declare + // an application logging location. + logCLI := flag.String("log", "", "Application log location") + + flag.Parse() + + // if a user passes in 'dev' log flag, override the + // App log to signal for stderr logging. + if *logCLI != "" { + *log = *logCLI + if *logCLI == "dev" { + *log = "" + } + } +} + +// SetFlagOverrides will add and check a `log` and `config` CLI flag to the +// current process, call flag.Parse() and will overwrite the passed in string +// pointer if the flag exists. +func SetFlagOverrides(log *string, config *string) { + // create the flag + cfg := flag.String("config", "", "Application log location") + SetLogOverride(log) + + // if a user passes in 'dev' log flag, override the + // App log to signal for stderr logging. + if *cfg != "" { + *config = *cfg + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/metrics/metrics.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/metrics/metrics.go new file mode 100644 index 000000000..ecd14a792 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/config/metrics/metrics.go @@ -0,0 +1,111 @@ +package metrics + +import ( + "time" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/metrics/dogstatsd" + "github.com/go-kit/kit/metrics/graphite" + "github.com/go-kit/kit/metrics/provider" + "github.com/go-kit/kit/metrics/statsd" + + "github.com/NYTimes/gizmo/config" +) + +// Type acts as an 'enum' type to represent +// the available metrics providers +type Type string + +const ( + // Statsd is used by config to indicate use of the statsdProvider. + Statsd Type = "statsd" + // DogStatsd is used by config to indicate use of the dogstatsdProvider. + DogStatsd Type = "dogstatsd" + // Prometheus is used by config to indicate use of the prometheusProvider. + Prometheus Type = "prometheus" + // Graphite is used by config to indicate use of the graphiteProvider. + Graphite Type = "graphite" + // Expvar is used by config to indicate use of the expvarProvider. + Expvar Type = "expvar" + // Discard is used by config to indicate use of the discardProvider. + Discard Type = "discard" +) + +// Config can be used to configure and instantiate a new +// go-kit/kit/metrics/provider.Provider. +type Config struct { + // if empty, will server default to "expvar" + Type Type `envconfig:"METRICS_TYPE"` + + // Prefix will be prefixed onto + // any metric name. + Prefix string `envconfig:"METRICS_PREFIX"` + + // Namespace is used by prometheus. + Namespace string `envconfig:"METRICS_NAMESPACE"` + // Subsystem is used by prometheus. + Subsystem string `envconfig:"METRICS_SUBSYSTEM"` + + // Used by statsd, graphite and dogstatsd + Interval time.Duration `envconfig:"METRICS_INTERVAL"` + + // Used by statsd, graphite and dogstatsd. + Addr string `envconfig:"METRICS_ADDR"` + // Used by statsd, graphite and dogstatsd to dial a connection. + // If empty, will default to "udp". + Network string `envconfig:"METRICS_NETWORK"` + + // Used by expvar only. + // if empty, will default to "/debug/vars" + Path string `envconfig:"METRICS_PATH"` + + // Used by graphite only. + // If none provided, kit/log/NewNopLogger will be used. + Logger log.Logger +} + +// LoadConfigFromEnv will attempt to load a Metrics object +// from environment variables. +func LoadConfigFromEnv() Config { + var mets Config + config.LoadEnvConfig(&mets) + return mets +} + +// NewProvider will use the values in the Metrics config object +// to generate a new go-kit/metrics/provider.Provider implementation. +// If no type is given, a no-op implementation will be used. +func (cfg Config) NewProvider() provider.Provider { + if cfg.Logger == nil { + cfg.Logger = log.NewNopLogger() + } + if cfg.Path == "" { + cfg.Path = "/debug/vars" + } + if cfg.Interval == 0 { + cfg.Interval = time.Second * 30 + } + switch cfg.Type { + case Statsd: + stsd := statsd.New(cfg.Prefix, cfg.Logger) + tick := time.NewTicker(cfg.Interval) + go stsd.SendLoop(tick.C, cfg.Network, cfg.Addr) + return provider.NewStatsdProvider(stsd, tick.Stop) + case DogStatsd: + stsd := dogstatsd.New(cfg.Prefix, cfg.Logger) + tick := time.NewTicker(cfg.Interval) + go stsd.SendLoop(tick.C, cfg.Network, cfg.Addr) + return provider.NewDogstatsdProvider(stsd, tick.Stop) + case Graphite: + grpht := graphite.New(cfg.Prefix, cfg.Logger) + tick := time.NewTicker(cfg.Interval) + go grpht.SendLoop(tick.C, cfg.Network, cfg.Addr) + return provider.NewGraphiteProvider(grpht, tick.Stop) + case Prometheus: + return provider.NewPrometheusProvider(cfg.Namespace, cfg.Subsystem) + case Expvar: + return provider.NewExpvarProvider() + default: + return provider.NewDiscardProvider() + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/coverage.sh b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/coverage.sh new file mode 100755 index 000000000..077a6d08b --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/coverage.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +workdir=.cover +profile="$workdir/cover.out" +mode=set + +generate_cover_data() { + rm -rf "$workdir" + mkdir "$workdir" + + for pkg in "$@"; do + if [ $pkg == "github.com/NYTimes/gizmo/server" -o $pkg == "github.com/NYTimes/gizmo/server/kit" -o $pkg == "github.com/NYTimes/gizmo/config" -o $pkg == "github.com/NYTimes/gizmo/web" -o $pkg == "github.com/NYTimes/gizmo/pubsub" ] + then + f="$workdir/$(echo $pkg | tr / -)" + go test -covermode="$mode" -coverprofile="$f.cover" "$pkg" + fi + done + + echo "mode: $mode" >"$profile" + grep -h -v "^mode:" "$workdir"/*.cover >>"$profile" +} + +show_cover_report() { + go tool cover -${1}="$profile" +} + +push_to_coveralls() { + goveralls -coverprofile="$profile" +} + +generate_cover_data $(go list ./...) +show_cover_report func +case "$1" in +"") + ;; +--html) + show_cover_report html ;; +--coveralls) + push_to_coveralls ;; +*) + echo >&2 "error: invalid option: $1" ;; +esac +rm -rf "$workdir" diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/doc.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/doc.go new file mode 100644 index 000000000..2a3a7fe54 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/doc.go @@ -0,0 +1,221 @@ +/* +Package gizmo is a toolkit that provides packages to put together server and pubsub daemons with the following features: + + * standardized configuration and logging + * health check endpoints with configurable strategies + * configuration for managing pprof endpoints and log levels + * structured logging containing basic request information + * useful metrics for endpoints + * graceful shutdowns + * basic interfaces to define our expectations and vocabulary + +## The `config` packages + +The `config` package contains a handful of useful functions to load to configuration structs from JSON files, JSON blobs in Consul k/v or environment variables. + +The subpackages contain structs meant for managing common configuration options and credentials. There are currently configs for: + + * Go Kit Metrics + * MySQL + * MongoDB + * Oracle + * AWS (S3, DynamoDB, ElastiCache) + * GCP + * Gorilla's `securecookie` + +The package also has a generic `Config` type in the `config/combined` subpackage that contains all of the above types. It's meant to be a 'catch all' convenience struct that many applications should be able to use. +The `server` package + +This package is the bulk of the toolkit and relies on `server.Config` for any managing `Server` implementations. A server must implement the following interface: + + // Server is the basic interface that defines what expect from any server. + type Server interface { + Register(Service) error + Start() error + Stop() error + } + +The package offers 2 server implementations: + +`SimpleServer`, which is capable of handling basic HTTP and JSON requests via 3 of the available `Service` implementations: `SimpleService`, `JSONService`, `ContextService`, `MixedService` and `MixedContextService`. A service and these implenetations will be defined below. + +`RPCServer`, which is capable of serving a gRPC server on one port and JSON endpoints on another. This kind of server can only handle the `RPCService` implementation. + +The `Service` interface is minimal to allow for maximum flexibility: + + type Service interface { + Prefix() string + + // Middleware provides a hook for service-wide middleware + Middleware(http.Handler) http.Handler + } + +The 3 service types that are accepted and hostable on the `SimpleServer`: + + type SimpleService interface { + Service + + // router - method - func + Endpoints() map[string]map[string]http.HandlerFunc + } + + type JSONService interface { + Service + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint + } + + type MixedService interface { + Service + + // route - method - func + Endpoints() map[string]map[string]http.HandlerFunc + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint + } + + type ContextService interface { + Service + + // route - method - func + ContextEndpoints() map[string]map[string]ContextHandlerFunc + // ContextMiddleware provides a hook for service-wide middleware around ContextHandler + ContextMiddleware(ContextHandler) ContextHandler + } + + type MixedContextService interface { + ContextService + + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + JSONContextMiddleware(JSONContextEndpoint) JSONContextEndpoint + } + +Where `JSONEndpoint`, `JSONContextEndpoint`, `ContextHandler` and `ContextHandlerFunc` are defined as: + + type JSONEndpoint func(*http.Request) (int, interface{}, error) + + type JSONContextEndpoint func(context.Context, *http.Request) (int, interface{}, error) + + type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) + } + + type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request) + +Also, the one service type that works with an `RPCServer`: + + type RPCService interface { + Service + + Service() (grpc.ServiceDesc, interface{}) + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONContextEndpoints. + JSONMiddlware(JSONContextEndpoint) JSONContextEndpoint + } + +The `Middleware(..)` functions offer each service a 'hook' to wrap each of its endpoints. This may be handy for adding additional headers or context to the request. This is also the point where other, third-party middleware could be easily be plugged in (ie. oauth, tracing, metrics, logging, etc.) + +The `pubsub` package + +This package contains two generic interfaces for publishing data to queues and subscribing and consuming data from those queues. + + // Publisher is a generic interface to encapsulate how we want our publishers + // to behave. Until we find reason to change, we're forcing all publishers + // to emit protobufs. + type Publisher interface { + // Publish will publish a message. + Publish(ctx context.Context, key string, msg proto.Message) error + // Publish will publish a []byte message. + PublishRaw(ctx context.Context, key string, msg []byte) error + } + + // Subscriber is a generic interface to encapsulate how we want our subscribers + // to behave. For now the system will auto stop if it encounters any errors. If + // a user encounters a closed channel, they should check the Err() method to see + // what happened. + type Subscriber interface { + // Start will return a channel of raw messages + Start() <-chan SubscriberMessage + // Err will contain any errors returned from the consumer connection. + Err() error + // Stop will initiate a graceful shutdown of the subscriber connection + Stop() error + } + +Where a `SubscriberMessage` is an interface that gives implementations a hook for acknowledging/delete messages. Take a look at the docs for each implementation in `pubsub` to see how they behave. + +There are currently 3 implementations of each type of `pubsub` interfaces: + +For pubsub via Amazon's SNS/SQS, you can use the `pubsub/aws` package. + +For pubsub via Google's Pubsub, you can use the `pubsub/gcp` package. + +For pubsub via Kafka topics, you can use the `pubsub/kafka` package. + +For publishing via HTTP, you can use the `pubsub/http` package. + +The `pubsub/pubsubtest` package + +This package contains 'test' implementations of the `pubsub.Publisher` and `pubsub.Subscriber` interfaces that will allow developers to easily mock out and test their `pubsub` implementations: + + type TestPublisher struct { + // Published will contain a list of all messages that have been published. + Published []TestPublishMsg + + // GivenError will be returned by the TestPublisher on publish. + // Good for testing error scenarios. + GivenError error + + // FoundError will contain any errors encountered while marshalling + // the protobuf struct. + FoundError error + } + + type TestSubscriber struct { + // ProtoMessages will be marshalled into []byte used to mock out + // a feed if it is populated. + ProtoMessages []proto.Message + + // JSONMEssages will be marshalled into []byte and used to mock out + // a feed if it is populated. + JSONMessages []interface{} + + // GivenErrError will be returned by the TestSubscriber on Err(). + // Good for testing error scenarios. + GivenErrError error + + // GivenStopError will be returned by the TestSubscriber on Stop(). + // Good for testing error scenarios. + GivenStopError error + + // FoundError will contain any errors encountered while marshalling + // the JSON and protobuf struct. + FoundError error + } + +The `web` package + +This package contains a handful of very useful functions for parsing types from request queries and payloads. + +Examples + +For examples of how to use the gizmo `server` and `pubsub` packages, take a look at the 'examples' subdirectory. + +The Gizmo logo was based on the Go mascot designed by Renée French and copyrighted under the Creative Commons Attribution 3.0 license. +*/ +package gizmo diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/gitcookies.sh.enc b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/gitcookies.sh.enc new file mode 100644 index 000000000..ba59a44ae --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/gitcookies.sh.enc @@ -0,0 +1 @@ +Fr›åuÒM¿ôavÚþ´žÜ¦ÊHLÛFñ›I’M›4Úõâ̪ØnýÑŒJ¾LI‹\—ó4A 4]ÁÓi_¿òˆ—óÄi™¡å9eæ¦Zö°ÂµEÊÞc‹Àh·YŸÂ-¬U´¿ÃŽ“dk>&¾†=ÿzP(Rh$g±œÂC¼cYDO.ÍŒâM&lZ†-ŽÃ½Èù³’ý?²qïgóÀñññ¹I íÇÞ¦TG³'ÁõÊMiïf:*˜³©àÎïQì+-÷Ê>ú*2‚ªL3»üY/RkiyÑ2£j8Ù¦°ÕÛ­Ž¶¿/Äöµ§1f%3×|e™¦r ó[O̜𕜠ôÖ'µÅfsKâçƒißµDÀÒ•ÃèWéÈŒ5ÄJ=sÁ+½õˆBëªQ³s,ÛJ7øK|0 c¾ÚÝКû¡Fš¬ Ì÷†ïJ—b¶ÂºÎû$¢fÜ“Aµ¥ÅÚš’E^,,-ÀS¹«=øç(Ÿ?è¹'×–¢·|BèBgôðȺ]E}'Ìký› UàØVI½ \ No newline at end of file diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity.go new file mode 100644 index 000000000..91ea706e0 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity.go @@ -0,0 +1,38 @@ +package server + +import "sync/atomic" + +// ActivityMonitor can be used to count and share the number of active requests. +type ActivityMonitor struct { + // counter for # of active requests + reqCount uint32 +} + +// NewActivityMonitor will return a new ActivityMonitor instance. +func NewActivityMonitor() *ActivityMonitor { + return &ActivityMonitor{} +} + +// Active returns true if there are requests currently in flight. +func (a *ActivityMonitor) Active() bool { + return a.NumActiveRequests() > 0 +} + +// CountRequest will increment the request count and signal +// the activity monitor to stay active. Call this in your server +// when you receive a request. +func (a *ActivityMonitor) CountRequest() { + atomic.AddUint32(&a.reqCount, 1) +} + +// UncountRequest will decrement the active request count. Best practice is to `defer` +// this function call directly after calling CountRequest(). +func (a *ActivityMonitor) UncountRequest() { + atomic.AddUint32(&a.reqCount, ^uint32(0)) +} + +// NumActiveRequests returns the number of in-flight requests currently +// running on this server. +func (a *ActivityMonitor) NumActiveRequests() uint32 { + return atomic.LoadUint32(&a.reqCount) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity_test.go new file mode 100644 index 000000000..b3d7b1595 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/activity_test.go @@ -0,0 +1,46 @@ +package server + +import "testing" + +func TestActivityMonitorRequestCount(t *testing.T) { + a := NewActivityMonitor() + // test the active request count + a.CountRequest() // 1 + a.CountRequest() // 2 + a.CountRequest() // 3 + + if !a.Active() { + t.Error("ActivityMonitor is inactive when there should be 3 active requests") + } + + if active := a.NumActiveRequests(); active != 3 { + t.Errorf("ActivityMonitor expected 3 active request, got %d", active) + } + + a.UncountRequest() // 2 + + if active := a.NumActiveRequests(); active != 2 { + t.Errorf("ActivityMonitor expected 2 active request, got %d", active) + } + + if !a.Active() { + t.Error("ActivityMonitor is inactive when there should be 2 active requests") + } + + a.UncountRequest() // 1 + + if active := a.NumActiveRequests(); active != 1 { + t.Errorf("ActivityMonitor expected 1 active request, got %d", active) + } + + if !a.Active() { + t.Error("ActivityMonitor is inactive when there should be 1 active request") + } + + a.UncountRequest() // 0 + + if a.Active() { + t.Error("ActivityMonitor is active when there should be 0 active request") + } + +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/config.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/config.go new file mode 100644 index 000000000..ee84dd1f3 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/config.go @@ -0,0 +1,170 @@ +package server + +import ( + "flag" + "io" + "net/http" + "os" + + "github.com/NYTimes/logrotate" + "github.com/go-kit/kit/metrics/provider" + "github.com/gorilla/handlers" + + "github.com/NYTimes/gizmo/config" + "github.com/NYTimes/gizmo/config/metrics" +) + +// Config holds info required to configure a gizmo server.Server. +type Config struct { + // Server will tell the server package which type of server to init. If + // empty, this will default to 'simple'. + ServerType string `envconfig:"GIZMO_SERVER_TYPE"` + + // HealthCheckType is used by server to init the proper HealthCheckHandler. + // If empty, this will default to 'simple'. + HealthCheckType string `envconfig:"GIZMO_HEALTH_CHECK_TYPE"` + // HealthCheckPath is used by server to init the proper HealthCheckHandler. + // If empty, this will default to '/status.txt'. + HealthCheckPath string `envconfig:"GIZMO_HEALTH_CHECK_PATH"` + // CustomHealthCheckHandler will be used if HealthCheckType is set with "custom". + CustomHealthCheckHandler http.Handler + + // RouterType is used by the server to init the proper Router implementation. + // If empty, this will default to 'gorilla'. + RouterType string `envconfig:"GIZMO_ROUTER_TYPE"` + + // JSONContentType can be used to override the default JSONContentType. + JSONContentType *string `envconfig:"GIZMO_JSON_CONTENT_TYPE"` + // MaxHeaderBytes can be used to override the default MaxHeaderBytes (1<<20). + MaxHeaderBytes *int `envconfig:"GIZMO_JSON_CONTENT_TYPE"` + // ReadTimeout can be used to override the default http server timeout of 10s. + // The string should be formatted like a time.Duration string. + ReadTimeout *string `envconfig:"GIZMO_READ_TIMEOUT"` + // WriteTimeout can be used to override the default http server timeout of 10s. + // The string should be formatted like a time.Duration string. + WriteTimeout *string `envconfig:"GIZMO_WRITE_TIMEOUT"` + // IdleTimeout can be used to override the default http server timeout of 120s. + // The string should be formatted like a time.Duration string. This + // feature is supported only on Go 1.8+. + IdleTimeout *string `envconfig:"GIZMO_IDLE_TIMEOUT"` + + // GOMAXPROCS can be used to override the default GOMAXPROCS (runtime.NumCPU). + GOMAXPROCS *int `envconfig:"GIZMO_SERVER_GOMAXPROCS"` + + // HTTPAccessLog is the location of the http access log. If it is empty, + // no access logging will be done. + HTTPAccessLog *string `envconfig:"HTTP_ACCESS_LOG"` + // RPCAccessLog is the location of the RPC access log. If it is empty, + // no access logging will be done. + RPCAccessLog *string `envconfig:"RPC_ACCESS_LOG"` + + // HTTPPort is the port the server implementation will serve HTTP over. + HTTPPort int `envconfig:"HTTP_PORT"` + // RPCPort is the port the server implementation will serve RPC over. + RPCPort int `envconfig:"RPC_PORT"` + + // Log is the path to the application log. + Log string `envconfig:"APP_LOG"` + // LogLevel will override the default log level of 'info'. + LogLevel string `envconfig:"APP_LOG_LEVEL"` + // LogJSONFormat will override the default JSON formatting logic on the server. + // By default the Server will log in a JSON format only if the Log field + // is defined. + // If this field is set and true, the logrus JSONFormatter will be used. + LogJSONFormat *bool `envconfig:"APP_LOG_JSON_FMT"` + + // TLSCertFile is an optional string for enabling TLS in simple servers. + TLSCertFile *string `envconfig:"TLS_CERT"` + // TLSKeyFile is an optional string for enabling TLS in simple servers. + TLSKeyFile *string `envconfig:"TLS_KEY"` + + // NotFoundHandler will override the default server NotfoundHandler if set. + NotFoundHandler http.Handler + + // Enable pprof Profiling. Off by default. + EnablePProf bool `envconfig:"ENABLE_PPROF"` + + // Metrics encapsulates the configurations required for a Gizmo + // Server to emit metrics. If your application has additional metrics, + // you should provide a MetricsFactory instead. + Metrics metrics.Config + // MetricsProvider will override the default server metrics provider if set. + MetricsProvider provider.Provider + + // GraphiteHost is DEPRECATED. Please use the + // Metrics config with "Type":"graphite" and this + // value in the "Addr" field. + GraphiteHost *string `envconfig:"GRAPHITE_HOST"` + + // this flag is for internal use. mainly to tell the SimpleServer + // to act like it's on an App Engine Flexible VM. + appEngine bool +} + +// LoadConfigFromEnv will attempt to load a Server object +// from environment variables. If not populated, nil +// is returned. +func LoadConfigFromEnv() *Config { + var server Config + config.LoadEnvConfig(&server) + server.Metrics = metrics.LoadConfigFromEnv() + return &server +} + +// NewAccessLogMiddleware will wrap a logrotate-aware Apache-style access log handler +// around the given http.Handler if an access log location is provided by the config, +// or optionally send access logs to stdout. +func NewAccessLogMiddleware(logLocation *string, handler http.Handler) (http.Handler, error) { + if logLocation == nil { + return handler, nil + } + var lw io.Writer + var err error + switch *logLocation { + case "stdout": + lw = os.Stdout + default: + lw, err = logrotate.NewFile(*logLocation) + if err != nil { + return nil, err + } + } + return handlers.CombinedLoggingHandler(lw, handler), nil +} + +// SetConfigOverrides will check the *CLI variables for any values +// and override the values in the given config if they are set. +// If LogCLI is set to "dev", the given `Log` pointer will be set to an +// empty string. +func SetConfigOverrides(c *Config) { + // HTTPAccessLogCLI is a pointer to the value of the '-http-access-log' command line flag. It is meant to + // declare an access log location for HTTP services. + HTTPAccessLogCLI := flag.String("http-access-log", "", "HTTP access log location") + // RPCAccessLogCLI is a pointer to the value of the '-rpc-access-log' command line flag. It is meant to + // declare an acces log location for RPC services. + RPCAccessLogCLI := flag.String("rpc-access-log", "", "RPC access log location") + // HTTPPortCLI is a pointer to the value for the '-http' flag. It is meant to declare the port + // number to serve HTTP services. + HTTPPortCLI := flag.Int("http", 0, "Port to run an HTTP server on") + // RPCPortCLI is a pointer to the value for the '-rpc' flag. It is meant to declare the port + // number to serve RPC services. + RPCPortCLI := flag.Int("rpc", 0, "Port to run an RPC server on") + + config.SetLogOverride(&c.Log) + + if *HTTPAccessLogCLI != "" { + c.HTTPAccessLog = HTTPAccessLogCLI + } + + if *RPCAccessLogCLI != "" { + c.RPCAccessLog = RPCAccessLogCLI + } + + if *HTTPPortCLI > 0 { + c.HTTPPort = *HTTPPortCLI + } + + if *RPCPortCLI > 0 { + c.RPCPort = *RPCPortCLI + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_gorilla.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_gorilla.go new file mode 100644 index 000000000..dfb850c43 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_gorilla.go @@ -0,0 +1,48 @@ +// +build !go1.7 + +package server + +import ( + "fmt" + "net/http" + + "github.com/gorilla/context" +) + +// AddIPToContext will attempt to pull an IP address out of the request and +// set it into a gorilla context. +func AddIPToContext(r *http.Request) { + ip, err := GetIP(r) + if err != nil { + LogWithFields(r).Warningf("unable to get IP: %s", err) + } else { + context.Set(r, "ip", ip) + } + + if ip = GetForwardedIP(r); len(ip) > 0 { + context.Set(r, "forward-for-ip", ip) + } +} + +// ContextFields will take a request and convert a context map to logrus Fields. +func ContextFields(r *http.Request) map[string]interface{} { + fields := map[string]interface{}{} + for k, v := range context.GetAll(r) { + strK := fmt.Sprintf("%+v", k) + typeK := fmt.Sprintf("%T-%+v", k, k) + // gorilla.mux adds the route to context. + // we want to remove it for now + if typeK == "mux.contextKey-1" || typeK == "mux.contextKey-0" { + continue + } + // web.varsKey for _all_ mux variables (gorilla or httprouter) + if typeK == "web.contextKey-2" { + strK = "muxvars" + } + fields[strK] = fmt.Sprintf("%#+v", v) + } + fields["path"] = r.URL.Path + fields["rawquery"] = r.URL.RawQuery + + return fields +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_native.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_native.go new file mode 100644 index 000000000..de1689262 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/context_native.go @@ -0,0 +1,55 @@ +// +build go1.7 + +package server + +import ( + "fmt" + "log" + "net/http" + + "github.com/NYTimes/gizmo/web" +) + +// AddIPToContext will attempt to pull an IP address out of the request and +// set it into a gorilla context. +func AddIPToContext(r *http.Request) { + ip, err := GetIP(r) + if err != nil { + LogWithFields(r).Warningf("unable to get IP: %s", err) + } else { + vars := web.Vars(r) + vars["ip"] = ip + web.SetRouteVars(r, vars) + } + + if ip = GetForwardedIP(r); len(ip) > 0 { + vars := web.Vars(r) + vars["forward-for-ip"] = ip + web.SetRouteVars(r, vars) + } +} + +// ContextFields will take a request and convert a context map to logrus Fields. +func ContextFields(r *http.Request) map[string]interface{} { + fields := map[string]interface{}{} + for k, v := range web.Vars(r) { + strK := fmt.Sprintf("%+v", k) + typeK := fmt.Sprintf("%T-%+v", k, k) + // gorilla.mux adds the route to context. + // we want to remove it for now + if typeK == "mux.contextKey-1" || typeK == "mux.contextKey-0" { + log.Print("mux key!?") + continue + } + // web.varsKey for _all_ mux variables (gorilla or httprouter) + if typeK == "web.contextKey-2" { + log.Print("muxvars key!?") + strK = "muxvars" + } + fields[strK] = fmt.Sprintf("%#+v", v) + } + fields["path"] = r.URL.Path + fields["rawquery"] = r.URL.RawQuery + + return fields +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/doc.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/doc.go new file mode 100644 index 000000000..84aa2f48f --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/doc.go @@ -0,0 +1,110 @@ +/* +Package server is the bulk of the toolkit and relies on `server.Config` for any managing `Server` implementations. A server must implement the following interface: + + // Server is the basic interface that defines what expect from any server. + type Server interface { + Register(...Service) error + Start() error + Stop() error + } + +The package offers 2 server implementations: + +`SimpleServer`, which is capable of handling basic HTTP and JSON requests via 3 of the available `Service` implementations: `SimpleService`, `JSONService`, `ContextService`, `MixedService` and `MixedContextService`. A service and these implementations will be defined below. + +`RPCServer`, which is capable of serving a gRPC server on one port and JSON endpoints on another. This kind of server can only handle the `RPCService` implementation. + +The `Service` interface is minimal to allow for maximum flexibility: + + type Service interface { + Prefix() string + + // Middleware provides a hook for service-wide middleware + Middleware(http.Handler) http.Handler + } + +The 3 service types that are accepted and hostable on the `SimpleServer`: + + type SimpleService interface { + Service + + // router - method - func + Endpoints() map[string]map[string]http.HandlerFunc + } + + type JSONService interface { + Service + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint + } + + type MixedService interface { + Service + + // route - method - func + Endpoints() map[string]map[string]http.HandlerFunc + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONEndpoints. + JSONMiddleware(JSONEndpoint) JSONEndpoint + } + + type ContextService interface { + Service + + // route - method - func + ContextEndpoints() map[string]map[string]ContextHandlerFunc + // ContextMiddleware provides a hook for service-wide middleware around ContextHandler + ContextMiddleware(ContextHandler) ContextHandler + } + + type MixedContextService interface { + ContextService + + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + JSONContextMiddleware(JSONContextEndpoint) JSONContextEndpoint + } + +Where `JSONEndpoint`, `JSONContextEndpoint`, `ContextHandler` and `ContextHandlerFunc` are defined as: + + type JSONEndpoint func(*http.Request) (int, interface{}, error) + + type JSONContextEndpoint func(context.Context, *http.Request) (int, interface{}, error) + + type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) + } + + type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request) + +Also, the one service type that works with an `RPCServer`: + + type RPCService interface { + ContextService + + Service() (grpc.ServiceDesc, interface{}) + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + // JSONMiddleware provides a hook for service-wide middleware around JSONContextEndpoints. + JSONMiddlware(JSONContextEndpoint) JSONContextEndpoint + } + +The `Middleware(..)` functions offer each service a 'hook' to wrap each of its endpoints. This may be handy for adding additional headers or context to the request. This is also the point where other, third-party middleware could be easily be plugged in (ie. oauth, tracing, metrics, logging, etc.) + +Examples + +Check out the gizmo/examples/servers directory to see several reference implementations. +*/ +package server diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck.go new file mode 100644 index 000000000..b914b7209 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck.go @@ -0,0 +1,151 @@ +package server + +import ( + "fmt" + "io" + "net/http" + "sync" + "sync/atomic" + "time" +) + +var ( + // ESXShutdownTimeout is the hard cut off kill the server while the ESXHealthCheck is waiting + // for the server to be inactive. + ESXShutdownTimeout = 180 * time.Second + // ESXShutdownPollInterval sets the duration for how long ESXHealthCheck will wait between + // each NumActiveRequests poll in WaitForZero. + ESXShutdownPollInterval = 1 * time.Second + // ESXLoadBalancerNotReadyDuration is the amount of time ESXHealthCheck will wait after + // sending a 'bad' status to the LB during a graceful shutdown. + ESXLoadBalancerNotReadyDuration = 15 * time.Second +) + +// ESXHealthCheck will manage the health checks and manage +// a server's load balanacer status. On Stop, it will block +// until all LBs have received a 'bad' status. +type ESXHealthCheck struct { + // ready flag health checks and graceful shutdown + // uint32 so we can use sync/atomic and no defers + ready uint32 + + // last LB status to know if LB knows we're inactive + lbNotReadyTime map[string]*time.Time + lbNotReadyTimeMu sync.RWMutex + + monitor *ActivityMonitor +} + +// NewESXHealthCheck returns a new instance of ESXHealthCheck. +func NewESXHealthCheck() *ESXHealthCheck { + return &ESXHealthCheck{ + lbNotReadyTime: map[string]*time.Time{}, + } +} + +// Path returns the default ESX health path. +func (e *ESXHealthCheck) Path() string { + return "/status.txt" +} + +// Start will set the monitor and flip the ready flag to 'True'. +func (e *ESXHealthCheck) Start(monitor *ActivityMonitor) error { + e.monitor = monitor + atomic.StoreUint32(&e.ready, 1) + return nil +} + +// Stop will set the flip the 'ready' flag and wait block until the server has removed itself +// from all load balancers. +func (e *ESXHealthCheck) Stop() error { + // fill the flag and wait + atomic.StoreUint32(&e.ready, 0) + if err := e.waitForZero(); err != nil { + Log.Errorf("server still active after %s, this will not be a graceful shutdown: %s", ESXShutdownTimeout, err) + return err + } + return nil +} + +// ServeHTTP will handle the health check requests on the server. ESXHealthCheck +// will return with an "ok" status as long as the ready flag is set to True. +// If a `deployer` query parameter is included, the request will not be counted +// as a load balancer. +func (e *ESXHealthCheck) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if atomic.LoadUint32(&e.ready) == 1 { + if _, err := io.WriteString(w, "ok-"+Name); err != nil { + LogWithFields(r).Warn("unable to write healthcheck response: ", err) + } + e.updateReadyTime(r, true) + } else { + http.Error(w, "service unavailable", http.StatusServiceUnavailable) + e.updateReadyTime(r, false) + } +} + +func (e *ESXHealthCheck) updateReadyTime(r *http.Request, ready bool) { + ip, err := GetIP(r) + if err != nil { + Log.Warnf("status endpoint was unable to get LB IP addr: %s", err) + return + } + + if _, ok := r.URL.Query()["deployer"]; !ok { + e.lbNotReadyTimeMu.Lock() + if ready { + e.lbNotReadyTime[ip] = nil + } else { + if last := e.lbNotReadyTime[ip]; last == nil { + now := time.Now() + e.lbNotReadyTime[ip] = &now + Log.Infof("status endpoint has returned it's first not-ready status to: %s", ip) + } + } + e.lbNotReadyTimeMu.Unlock() + } +} + +func (e *ESXHealthCheck) lbActive() (active bool) { + e.lbNotReadyTimeMu.RLock() + defer e.lbNotReadyTimeMu.RUnlock() + for ip, notReadyTime := range e.lbNotReadyTime { + if notReadyTime == nil || time.Since(*notReadyTime) < ESXLoadBalancerNotReadyDuration { + Log.Infof("load balancer is still active: %s", ip) + return true + } + } + return false +} + +// waitForZero will continuously query Active and NumActiveRequests at the ShutdownPollInterval until the +// LB has seen a bad status, the server is not Actve and NumActiveRequests returns 0 or the timeout +// is reached. It will return error in case of timeout. +func (e *ESXHealthCheck) waitForZero() error { + to := time.After(ESXShutdownTimeout) + done := make(chan struct{}, 1) + go func() { + for { + if e.monitor.Active() || e.lbActive() { + Log.Info("server is still active") + } else { + Log.Info("server is no longer active") + reqs := e.monitor.NumActiveRequests() + if reqs == 0 { + done <- struct{}{} + break + } else { + Log.Info("server still has requests in flight. waiting longer...") + } + } + time.Sleep(ESXShutdownPollInterval) + } + }() + + select { + case <-done: + Log.Info("server is no longer receiving traffic") + return nil + case <-to: + return fmt.Errorf("server is still active after %s", ESXShutdownTimeout) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck_test.go new file mode 100644 index 000000000..ec69dffba --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/esxhealthcheck_test.go @@ -0,0 +1,242 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +func TestESXHealthCheckDeployer(t *testing.T) { + // changing so the tests don't take forever, but long enough so tests fail + ESXLoadBalancerNotReadyDuration = 10 * time.Second + + hc := NewESXHealthCheck() + am := NewActivityMonitor() + hc.Start(am) + + // setup an LB-like request with an easy IP header + lbIP := "1.1.1.1" + lbReq, _ := http.NewRequest("GET", hc.Path()+"?deployer=true", nil) + lbReq.Header.Add("X-Real-IP", lbIP) + + wr := httptest.NewRecorder() + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusOK { + t.Errorf("ESXHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("ESXHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + + // gorountine to make sure Stop actually stops on time + done := make(chan bool) + go func() { + stopped := make(chan bool) + go func() { + hc.Stop() + stopped <- true + }() + select { + case <-stopped: + case <-time.After(2 * time.Second): + t.Errorf("ESXHealthCheck.Stop() was still stopping after 2 seconds with no LBs checking!") + } + done <- true + }() + // give it a moment + time.Sleep(20 * time.Millisecond) + + wr = httptest.NewRecorder() + + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusServiceUnavailable { + t.Errorf("ESXHealthCheck expected 503 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); gotBody != "service unavailable\n" { + t.Errorf("ESXHealthCheck expected response body to start with 'service unavailable', got %s", gotBody) + } + + <-done +} + +func TestESXHealthCheckLB(t *testing.T) { + hc := NewESXHealthCheck() + am := NewActivityMonitor() + // changing so the tests don't take forever + ESXLoadBalancerNotReadyDuration = 1 * time.Second + + hc.Start(am) + + // setup an LB-like request with an easy IP header + lbIP := "1.1.1.1" + lbReq, _ := http.NewRequest("GET", hc.Path(), nil) + lbReq.Header.Add("X-Real-IP", lbIP) + + wr := httptest.NewRecorder() + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusOK { + t.Errorf("ESXHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("ESXHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + + // gorountine to make sure Stop actually stops on time + done := make(chan bool) + go func() { + stopped := make(chan bool) + go func() { + hc.Stop() + stopped <- true + }() + select { + case <-stopped: + case <-time.After(ESXLoadBalancerNotReadyDuration * (2 * time.Second)): + t.Errorf("ESXHealthCheck.Stop() was still stopping 2x after ESXLoadBalancerNotReadyDuration") + } + done <- true + }() + // give it a moment + time.Sleep(20 * time.Millisecond) + + // let the LB see a bad request + wr = httptest.NewRecorder() + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusServiceUnavailable { + t.Errorf("ESXHealthCheck expected 503 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); gotBody != "service unavailable\n" { + t.Errorf("ESXHealthCheck expected response body to start with 'service unavailable', got %s", gotBody) + } + + // wait for the healthcheck to stop + <-done +} + +func TestESXHealthCheckActiveRequests(t *testing.T) { + // changing so the tests don't take forever, but long enough so tests fail + ESXLoadBalancerNotReadyDuration = 5 * time.Second + ESXShutdownTimeout = 5 * time.Second + + hc := NewESXHealthCheck() + am := NewActivityMonitor() + hc.Start(am) + + // setup an LB-like request with an easy IP header + lbIP := "1.1.1.1" + lbReq, _ := http.NewRequest("GET", hc.Path(), nil) + lbReq.Header.Add("X-Real-IP", lbIP) + + wr := httptest.NewRecorder() + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusOK { + t.Errorf("ESXHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("ESXHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + + // WHOA, AN ACTIVE REQUEST ENTERS THE SCENE FROM STAGE LEFT! + am.CountRequest() + + // gorountine to make sure Stop actually stops on time + done := make(chan bool) + go func() { + stopped := make(chan bool) + go func() { + err := hc.Stop() + if err == nil { + t.Error("ESXHealthCheck expected a shutdown timeout error because there was an active request") + } + stopped <- true + }() + select { + case <-stopped: + case <-time.After(ESXShutdownTimeout * 2): + t.Errorf("ESXHealthCheck.Stop() was still stopping after 2 seconds with no LBs checking!") + } + done <- true + }() + // give it a moment + time.Sleep(20 * time.Millisecond) + + wr = httptest.NewRecorder() + + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusServiceUnavailable { + t.Errorf("ESXHealthCheck expected 503 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); gotBody != "service unavailable\n" { + t.Errorf("ESXHealthCheck expected response body to start with 'service unavailable', got %s", gotBody) + } + + <-done +} +func TestESXHealthCheckBadIP(t *testing.T) { + // changing so the tests don't take forever, but long enough so tests fail + ESXLoadBalancerNotReadyDuration = 10 * time.Second + + hc := NewESXHealthCheck() + am := NewActivityMonitor() + hc.Start(am) + + // setup an LB-like request with an easy IP header + lbReq, _ := http.NewRequest("GET", hc.Path(), nil) + + wr := httptest.NewRecorder() + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusOK { + t.Errorf("ESXHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("ESXHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + + // gorountine to make sure Stop actually stops on time + done := make(chan bool) + go func() { + stopped := make(chan bool) + go func() { + hc.Stop() + stopped <- true + }() + select { + case <-stopped: + case <-time.After(2 * time.Second): + t.Errorf("ESXHealthCheck.Stop() was still stopping after 2 seconds with no LBs checking!") + } + done <- true + }() + // give it a moment + time.Sleep(20 * time.Millisecond) + + wr = httptest.NewRecorder() + + hc.ServeHTTP(wr, lbReq) + + if wr.Code != http.StatusServiceUnavailable { + t.Errorf("ESXHealthCheck expected 503 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); gotBody != "service unavailable\n" { + t.Errorf("ESXHealthCheck expected response body to start with 'service unavailable', got %s", gotBody) + } + + <-done +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck.go new file mode 100644 index 000000000..907378a36 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck.go @@ -0,0 +1,84 @@ +package server + +import ( + "io" + "net/http" +) + +// HealthCheckHandler is an interface used by SimpleServer and RPCServer +// to allow users to customize their service's health check. Start will be +// called just before server start up and the given ActivityMonitor should +// offer insite to the # of requests in flight, if needed. +// Stop will be called once the servers receive a kill signal. +type HealthCheckHandler interface { + http.Handler + Path() string + Start(*ActivityMonitor) error + Stop() error +} + +// SimpleHealthCheck is a basic HealthCheckHandler implementation +// that _always_ returns with an "ok" status and shuts down immediately. +type SimpleHealthCheck struct { + path string +} + +// NewSimpleHealthCheck will return a new SimpleHealthCheck instance. +func NewSimpleHealthCheck(path string) *SimpleHealthCheck { + return &SimpleHealthCheck{path: path} +} + +// Path will return the configured status path to server on. +func (s *SimpleHealthCheck) Path() string { + return s.path +} + +// Start will do nothing. +func (s *SimpleHealthCheck) Start(monitor *ActivityMonitor) error { + return nil +} + +// Stop will do nothing and return nil. +func (s *SimpleHealthCheck) Stop() error { + return nil +} + +// ServeHTTP will always respond with "ok-"+server.Name. +func (s *SimpleHealthCheck) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if _, err := io.WriteString(w, "ok-"+Name); err != nil { + LogWithFields(r).Warn("unable to write healthcheck response: ", err) + } +} + +// NewCustomHealthCheck will return a new CustomHealthCheck with the given +// path and handler. +func NewCustomHealthCheck(path string, handler http.Handler) *CustomHealthCheck { + return &CustomHealthCheck{path, handler} +} + +// CustomHealthCheck is a HealthCheckHandler that uses +// a custom http.Handler provided to the server via `config.CustomHealthCheckHandler`. +type CustomHealthCheck struct { + path string + handler http.Handler +} + +// Path will return the configured status path to server on. +func (c *CustomHealthCheck) Path() string { + return c.path +} + +// Start will do nothing. +func (c *CustomHealthCheck) Start(monitor *ActivityMonitor) error { + return nil +} + +// Stop will do nothing and return nil. +func (c *CustomHealthCheck) Stop() error { + return nil +} + +// ServeHTTP will allow the custom handler to manage the request and response. +func (c *CustomHealthCheck) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c.handler.ServeHTTP(w, r) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck_test.go new file mode 100644 index 000000000..e562231d7 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/healthcheck_test.go @@ -0,0 +1,43 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestSimpleHealthCheck(t *testing.T) { + path := "/status.txt" + s := NewSimpleHealthCheck(path) + a := NewActivityMonitor() + s.Start(a) + + req, _ := http.NewRequest("GET", path, nil) + wr := httptest.NewRecorder() + + s.ServeHTTP(wr, req) + + if wr.Code != http.StatusOK { + t.Errorf("SimpleHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("SimpleHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + + s.Stop() + + wr = httptest.NewRecorder() + + s.ServeHTTP(wr, req) + + if wr.Code != http.StatusOK { + t.Errorf("SimpleHealthCheck expected 200 response code, got %d", wr.Code) + } + + if gotBody := wr.Body.String(); !strings.HasPrefix(gotBody, "ok-") { + t.Errorf("SimpleHealthCheck expected response body to start with 'ok-', got %s", gotBody) + } + +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server.go new file mode 100644 index 000000000..4177af40f --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server.go @@ -0,0 +1,14 @@ +// +build !go1.8 + +package server + +import "net/http" + +func httpServer(handler http.Handler) *http.Server { + return &http.Server{ + Handler: handler, + MaxHeaderBytes: maxHeaderBytes, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server_go18.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server_go18.go new file mode 100644 index 000000000..a8fdf7c38 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/http_server_go18.go @@ -0,0 +1,15 @@ +// +build go1.8 + +package server + +import "net/http" + +func httpServer(handler http.Handler) *http.Server { + return &http.Server{ + Handler: handler, + MaxHeaderBytes: maxHeaderBytes, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + IdleTimeout: idleTimeout, + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/README.md b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/README.md new file mode 100644 index 000000000..4243bd966 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/README.md @@ -0,0 +1,11 @@ +# This is an experimental package in Gizmo! + +* The rationale behind this package: + * A more opinionated server with fewer choices. + * go-kit is used for serving HTTP/JSON & gRPC is used for serving HTTP2/RPC + * Monitoring and metrics are handled by a sidecar (ie. Cloud Endpoints) + * Logs always go to stdout/stderr + * Using Go's 1.8 graceful HTTP shutdown + * Services using this package are meant for deploy to GCP with GKE and Cloud Endpoints. + +* If you experience any issues please create an issue and/or reach out on the #gizmo channel with what you've found diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/config.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/config.go new file mode 100644 index 000000000..603a1e414 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/config.go @@ -0,0 +1,83 @@ +package kit + +import ( + "runtime" + "time" + + "github.com/kelseyhightower/envconfig" +) + +// Config holds info required to configure a gizmo kit.Server. +// +// This struct is loaded from the environment at Run and only made public to expose +// for documentation. +type Config struct { + // HealthCheckPath is used by server to init the proper HealthCheckHandler. + // If empty, this will default to '/healthz'. + HealthCheckPath string `envconfig:"GIZMO_HEALTH_CHECK_PATH"` + + // MaxHeaderBytes can be used to override the default of 1<<20. + MaxHeaderBytes int `envconfig:"GIZMO_MAX_HEADER_BYTES"` + + // ReadTimeout can be used to override the default http server timeout of 10s. + // The string should be formatted like a time.Duration string. + ReadTimeout time.Duration `envconfig:"GIZMO_READ_TIMEOUT"` + + // WriteTimeout can be used to override the default http server timeout of 10s. + // The string should be formatted like a time.Duration string. + WriteTimeout time.Duration `envconfig:"GIZMO_WRITE_TIMEOUT"` + + // IdleTimeout can be used to override the default http server timeout of 120s. + // The string should be formatted like a time.Duration string. + IdleTimeout time.Duration `envconfig:"GIZMO_IDLE_TIMEOUT"` + + // ShutdownTimeout can be used to override the default http server shutdown timeout + // of 5m. + ShutdownTimeout time.Duration `envconfig:"GIZMO_SHUTDOWN_TIMEOUT"` + + // GOMAXPROCS can be used to override the default GOMAXPROCS. + GOMAXPROCS int `envconfig:"GIZMO_GOMAXPROCS"` + + // HTTPPort is the port the server implementation will serve HTTP over. + // The default is 8080 + HTTPPort int `envconfig:"HTTP_PORT"` + // RPCPort is the port the server implementation will serve RPC over. + // The default is 8081. + RPCPort int `envconfig:"RPC_PORT"` + + // Enable pprof Profiling. Off by default. + EnablePProf bool `envconfig:"ENABLE_PPROF"` +} + +func loadConfig() Config { + var cfg Config + envconfig.MustProcess("", &cfg) + if cfg.HTTPPort == 0 { + cfg.HTTPPort = 8080 + } + if cfg.RPCPort == 0 { + cfg.RPCPort = 8081 + } + if cfg.MaxHeaderBytes == 0 { + cfg.MaxHeaderBytes = 1 << 20 + } + if cfg.ReadTimeout.Nanoseconds() == 0 { + cfg.ReadTimeout = 10 * time.Second + } + if cfg.IdleTimeout.Nanoseconds() == 0 { + cfg.IdleTimeout = 120 * time.Second + } + if cfg.WriteTimeout.Nanoseconds() == 0 { + cfg.WriteTimeout = 10 * time.Second + } + if cfg.GOMAXPROCS > 0 { + runtime.GOMAXPROCS(cfg.GOMAXPROCS) + } + if cfg.HealthCheckPath == "" { + cfg.HealthCheckPath = "/healthz" + } + if cfg.ShutdownTimeout.Nanoseconds() == 0 { + cfg.ShutdownTimeout = 5 * time.Minute + } + return cfg +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver.go new file mode 100644 index 000000000..cbc92d6cf --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver.go @@ -0,0 +1,214 @@ +package kit + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "os" + + "github.com/go-kit/kit/log" + httptransport "github.com/go-kit/kit/transport/http" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/pkg/errors" + ocontext "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// Server encapsulates all logic for registering and running a gizmo kit server. +type Server struct { + logger log.Logger + + mux Router + + cfg Config + + svr *http.Server + gsvr *grpc.Server + + // exit chan for graceful shutdown + exit chan chan error +} + +type contextKey int + +const ( + // key to set/retrieve URL params from a request context. + varsKey contextKey = iota + // key for logger + logKey +) + +// NewServer will create a new kit server for the given Service. +// +// Generally, users should only use the 'Run' function to start a server and use this +// function within tests so they may call ServeHTTP. +func NewServer(svc Service) *Server { + // load config from environment with defaults set + cfg := loadConfig() + + ropts := svc.HTTPRouterOptions() + // default the router if none set + if len(ropts) == 0 { + ropts = append(ropts, RouterSelect("")) + } + var r Router + for _, opt := range ropts { + r = opt(r) + } + + s := &Server{ + cfg: cfg, + mux: r, + exit: make(chan chan error), + logger: log.NewJSONLogger(log.NewSyncWriter(os.Stdout)), + } + s.svr = &http.Server{ + Handler: s, + Addr: fmt.Sprintf(":%d", cfg.HTTPPort), + MaxHeaderBytes: cfg.MaxHeaderBytes, + ReadTimeout: cfg.ReadTimeout, + WriteTimeout: cfg.WriteTimeout, + IdleTimeout: cfg.IdleTimeout, + } + s.register(svc) + return s +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // hand the request off to the router + s.mux.ServeHTTP(w, r) +} + +func (s *Server) register(svc Service) { + opts := []httptransport.ServerOption{ + // populate context with helpful keys + httptransport.ServerBefore( + httptransport.PopulateRequestContext, + ), + // inject the server logger into every request context + httptransport.ServerBefore(func(ctx context.Context, _ *http.Request) context.Context { + return context.WithValue(ctx, logKey, s.logger) + }), + } + opts = append(opts, svc.HTTPOptions()...) + + var healthzFound bool + // register all endpoints with our wrappers & default decoders/encoders + for path, epMethods := range svc.HTTPEndpoints() { + for method, ep := range epMethods { + + // check if folks are supplying their own healthcheck + if method == http.MethodGet && path == s.cfg.HealthCheckPath { + healthzFound = true + } + + // just pass the http.Request in if no decoder provided + if ep.Decoder == nil { + ep.Decoder = func(_ context.Context, r *http.Request) (interface{}, error) { + return r, nil + } + } + // default to the httptransport helper + if ep.Encoder == nil { + ep.Encoder = httptransport.EncodeJSONResponse + } + s.mux.Handle(method, path, svc.HTTPMiddleware( + httptransport.NewServer( + svc.Middleware(ep.Endpoint), + ep.Decoder, + ep.Encoder, + append(opts, ep.Options...)...))) + } + } + + // register a simple health check if none provided + if !healthzFound { + s.mux.HandleFunc(http.MethodGet, s.cfg.HealthCheckPath, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + io.WriteString(w, "OK") + }) + } + + gdesc := svc.RPCServiceDesc() + if gdesc == nil { + return + } + + inters := []grpc.UnaryServerInterceptor{ + grpc.UnaryServerInterceptor( + // inject logger into gRPC server and hook in go-kit middleware + func(ctx ocontext.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + ctx = context.WithValue(ctx, logKey, s.logger) + return svc.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return handler(ctx, req) + })(ctx, req) + }, + ), + } + if mw := svc.RPCMiddleware(); mw != nil { + inters = append(inters, mw) + } + + s.gsvr = grpc.NewServer(append(svc.RPCOptions(), + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(inters...)))...) + + s.gsvr.RegisterService(gdesc, svc) +} + +func (s *Server) start() error { + go func() { + err := s.svr.ListenAndServe() + if err != nil { + s.logger.Log( + "error", err, + "msg", "HTTP server error - initiating shutting down") + s.stop() + } + }() + + s.logger.Log("msg", + fmt.Sprintf("listening on HTTP port: %d", s.cfg.HTTPPort)) + + if s.gsvr != nil { + gaddr := fmt.Sprintf(":%d", s.cfg.RPCPort) + lis, err := net.Listen("tcp", gaddr) + if err != nil { + return errors.Wrap(err, "failed to listen to RPC port") + } + + go func() { + err := s.gsvr.Serve(lis) + if err != nil { + s.logger.Log( + "error", err, + "msg", "gRPC server error - initiating shutting down") + s.stop() + } + }() + s.logger.Log("msg", + fmt.Sprintf("listening on RPC port: %d", s.cfg.RPCPort)) + } + + go func() { + exit := <-s.exit + + // stop the listener with timeout + ctx, cancel := context.WithTimeout(context.Background(), s.cfg.ShutdownTimeout) + defer cancel() + + if s.gsvr != nil { + s.gsvr.GracefulStop() + } + exit <- s.svr.Shutdown(ctx) + }() + + return nil +} + +func (s *Server) stop() error { + ch := make(chan error) + s.exit <- ch + return <-ch +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_pb_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_pb_test.go new file mode 100644 index 000000000..963c3a4e1 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_pb_test.go @@ -0,0 +1,181 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: kitserver_test.proto + +/* +Package kit_test is a generated protocol buffer package. + +It is generated from these files: + kitserver_test.proto + +It has these top-level messages: + GetCatNameRequest + Cat +*/ +package kit_test + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "google.golang.org/genproto/googleapis/api/annotations" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type GetCatNameRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *GetCatNameRequest) Reset() { *m = GetCatNameRequest{} } +func (m *GetCatNameRequest) String() string { return proto.CompactTextString(m) } +func (*GetCatNameRequest) ProtoMessage() {} +func (*GetCatNameRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *GetCatNameRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type Cat struct { + Age int32 `protobuf:"varint,3,opt,name=Age" json:"Age,omitempty"` + Breed string `protobuf:"bytes,1,opt,name=Breed" json:"Breed,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name" json:"Name,omitempty"` +} + +func (m *Cat) Reset() { *m = Cat{} } +func (m *Cat) String() string { return proto.CompactTextString(m) } +func (*Cat) ProtoMessage() {} +func (*Cat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *Cat) GetAge() int32 { + if m != nil { + return m.Age + } + return 0 +} + +func (m *Cat) GetBreed() string { + if m != nil { + return m.Breed + } + return "" +} + +func (m *Cat) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func init() { + proto.RegisterType((*GetCatNameRequest)(nil), "kit_test.GetCatNameRequest") + proto.RegisterType((*Cat)(nil), "kit_test.Cat") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for KitTestService service + +type KitTestServiceClient interface { + GetCatName(ctx context.Context, in *GetCatNameRequest, opts ...grpc.CallOption) (*Cat, error) +} + +type kitTestServiceClient struct { + cc *grpc.ClientConn +} + +func NewKitTestServiceClient(cc *grpc.ClientConn) KitTestServiceClient { + return &kitTestServiceClient{cc} +} + +func (c *kitTestServiceClient) GetCatName(ctx context.Context, in *GetCatNameRequest, opts ...grpc.CallOption) (*Cat, error) { + out := new(Cat) + err := grpc.Invoke(ctx, "/kit_test.Kit_testService/GetCatName", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for KitTestService service + +type KitTestServiceServer interface { + GetCatName(context.Context, *GetCatNameRequest) (*Cat, error) +} + +func RegisterKitTestServiceServer(s *grpc.Server, srv KitTestServiceServer) { + s.RegisterService(&_KitTestService_serviceDesc, srv) +} + +func _KitTestService_GetCatName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCatNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KitTestServiceServer).GetCatName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kit_test.Kit_testService/GetCatName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KitTestServiceServer).GetCatName(ctx, req.(*GetCatNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _KitTestService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "kit_test.Kit_testService", + HandlerType: (*KitTestServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCatName", + Handler: _KitTestService_GetCatName_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kitserver_test.proto", +} + +func init() { proto.RegisterFile("kitserver_test.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 227 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc9, 0xce, 0x2c, 0x29, + 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0x8a, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0xe2, 0xc8, 0xce, 0x2c, 0x01, 0xf3, 0xa5, 0x64, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, + 0x13, 0x0b, 0x32, 0xf5, 0x13, 0xf3, 0xf2, 0xf2, 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x8a, 0x21, + 0xea, 0x94, 0xd4, 0xb9, 0x04, 0xdd, 0x53, 0x4b, 0x9c, 0x13, 0x4b, 0xfc, 0x12, 0x73, 0x53, 0x83, + 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25, 0x18, + 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0x25, 0x47, 0x2e, 0x66, 0xe7, 0xc4, 0x12, 0x21, 0x01, + 0x2e, 0x66, 0xc7, 0xf4, 0x54, 0x09, 0x66, 0x05, 0x46, 0x0d, 0xd6, 0x20, 0x10, 0x53, 0x48, 0x84, + 0x8b, 0xd5, 0xa9, 0x28, 0x35, 0x35, 0x05, 0xaa, 0x1a, 0xc2, 0x01, 0x19, 0x01, 0x32, 0x51, 0x82, + 0x09, 0x62, 0x04, 0x88, 0x6d, 0x94, 0xc2, 0xc5, 0xef, 0x0d, 0x75, 0x55, 0x70, 0x6a, 0x51, 0x59, + 0x66, 0x72, 0xaa, 0x50, 0x20, 0x17, 0x17, 0xc2, 0x7a, 0x21, 0x69, 0x3d, 0x98, 0xab, 0xf5, 0x30, + 0x1c, 0x25, 0xc5, 0x8b, 0x90, 0x74, 0x4e, 0x2c, 0x51, 0x12, 0x6f, 0xba, 0xfc, 0x64, 0x32, 0x93, + 0xa0, 0x10, 0xbf, 0x7e, 0x71, 0x59, 0xb2, 0x7e, 0x72, 0x62, 0x89, 0x7e, 0x35, 0xc8, 0x9d, 0xb5, + 0x49, 0x6c, 0x60, 0x8f, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xdb, 0x7d, 0x9c, 0x18, + 0x01, 0x00, 0x00, +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.go new file mode 100644 index 000000000..5b5c29266 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.go @@ -0,0 +1,152 @@ +package kit_test + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "reflect" + "syscall" + "testing" + "time" + + "github.com/go-kit/kit/endpoint" + httptransport "github.com/go-kit/kit/transport/http" + ocontext "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/NYTimes/gizmo/server/kit" + "github.com/NYTimes/gziphandler" +) + +func TestKitServer(t *testing.T) { + go func() { + // runs the HTTP _AND_ gRPC servers + err := kit.Run(&server{}) + if err != nil { + t.Fatal("problems running service: " + err.Error()) + } + }() + + // let the server start + time.Sleep(1 * time.Second) + + // hit the health check + resp, err := http.Get("http://localhost:8080/healthz") + if err != nil { + t.Fatal("unable to hit health check:", err) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal("unable to read health check response:", err) + } + + if string(b) != "OK" { + t.Fatalf("unexpected health check response. got %q, wanted 'OK'", string(b)) + } + + // hit the HTTP server + resp, err = http.Get("http://localhost:8080/svc/cat/ziggy") + if err != nil { + t.Fatal("unable to cat http endpoint:", err) + } + + var hcat Cat + err = json.NewDecoder(resp.Body).Decode(&hcat) + if err != nil { + t.Fatal("unable to read JSON cat:", err) + } + + if !reflect.DeepEqual(&hcat, testCat) { + t.Fatalf("expected cat: %#v, got %#v", testCat, hcat) + } + + // hit the gRPC server + gc, err := grpc.Dial("localhost:8081", grpc.WithInsecure()) + if err != nil { + t.Fatalf("unable to init gRPC connection: %s", err) + } + defer gc.Close() + cc := NewKitTestServiceClient(gc) + cat, err := cc.GetCatName(context.Background(), &GetCatNameRequest{Name: "ziggy"}) + if err != nil { + t.Fatalf("unable to make gRPC request: %s", err) + } + + if !reflect.DeepEqual(cat, testCat) { + t.Fatalf("expected cat: %#v, got %#v", testCat, cat) + } + + // make signal to kill server + syscall.Kill(syscall.Getpid(), syscall.SIGTERM) +} + +type server struct{} + +func (s *server) Middleware(e endpoint.Endpoint) endpoint.Endpoint { + return endpoint.Endpoint(func(ctx context.Context, r interface{}) (interface{}, error) { + kit.LogMsgWithFields(ctx, "kit middleware!") + return e(ctx, r) + }) +} + +func (s *server) HTTPMiddleware(h http.Handler) http.Handler { + return gziphandler.GzipHandler(h) +} + +func (s *server) HTTPOptions() []httptransport.ServerOption { + return nil +} + +func (s *server) HTTPRouterOptions() []kit.RouterOption { + return nil +} + +func (s *server) HTTPEndpoints() map[string]map[string]kit.HTTPEndpoint { + return map[string]map[string]kit.HTTPEndpoint{ + "/svc/cat/{name:[a-zA-Z]+}": { + "GET": { + Endpoint: s.getCatByName, + Decoder: catNameDecoder, + }, + }, + } +} + +func (s *server) RPCServiceDesc() *grpc.ServiceDesc { + return &_KitTestService_serviceDesc +} + +func (s *server) RPCMiddleware() grpc.UnaryServerInterceptor { + return grpc.UnaryServerInterceptor(func(ctx ocontext.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + kit.LogMsgWithFields(ctx, "rpc middleware!") + return handler(ctx, req) + }) +} + +func (s *server) RPCOptions() []grpc.ServerOption { + return nil +} + +// gRPC layer +func (s *server) GetCatName(ctx ocontext.Context, r *GetCatNameRequest) (*Cat, error) { + rs, err := s.getCatByName(ctx, r) + if err != nil { + return nil, err + } + return rs.(*Cat), nil +} + +// http decoder layer +func catNameDecoder(ctx context.Context, r *http.Request) (interface{}, error) { + return &GetCatNameRequest{Name: kit.Vars(r)["name"]}, nil +} + +var testCat = &Cat{Breed: "American Shorthair", Name: "Ziggy", Age: 12} + +// shared business layer +func (s *server) getCatByName(ctx context.Context, _ interface{}) (interface{}, error) { + kit.Logger(ctx).Log("message", "getting ziggy") + return testCat, nil +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.proto b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.proto new file mode 100644 index 000000000..0aab5aa85 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +import "google/api/annotations.proto"; + +package kit_test; + +message GetCatNameRequest { + string name = 1; +} + +message Cat { + int32 Age = 3; + string Breed = 1; + string Name = 2; +} + +service Kit_testService { + rpc GetCatName(GetCatNameRequest) returns (Cat) { + option (google.api.http) = { + get: "/svc/cat/{name}" + }; + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.yaml b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.yaml new file mode 100644 index 000000000..718744ff9 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/kitserver_test.yaml @@ -0,0 +1,39 @@ +swagger: '2.0' +info: + title: kit_test + description: A test service for gizmo/server/kit + version: "1.0.0" +host: gizmo.nyt.net +schemes: + - https +basePath: /svc/ +paths: + cat/{name}: + get: + parameters: + - name: name + in: path + required: true + type: string + responses: + 200: + schema: + $ref: '#/definitions/Cat' + 500: + schema: + $ref: '#/definitions/Cat' + +definitions: + Cat: + type: object + properties: + Breed: + type: string + x-proto-tag: 1 + Name: + type: string + x-proto-tag: 2 + Age: + type: number + format: int32 + x-proto-tag: 3 diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/log.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/log.go new file mode 100644 index 000000000..acc1f506e --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/log.go @@ -0,0 +1,77 @@ +package kit + +import ( + "context" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/transport/http" + "google.golang.org/grpc/metadata" +) + +// Logger will return a kit/log.Logger that has been injected +// into the context by the kit server. This function will only work +// within the scope of a request initiated by the server. +func Logger(ctx context.Context) log.Logger { + return ctx.Value(logKey).(log.Logger) +} + +// Log will pull the server log.Logger from the context and +// log the given keyvals with it. +func Log(ctx context.Context, keyvals ...interface{}) error { + return Logger(ctx).Log(keyvals...) +} + +// LoggerWithFields will pull any known request info from the context +// and include it into the log as key values. +func LoggerWithFields(ctx context.Context) log.Logger { + l := Logger(ctx) + // for HTTP requests + keys := map[interface{}]string{ + http.ContextKeyRequestMethod: "http-method", + http.ContextKeyRequestURI: "http-uri", + http.ContextKeyRequestPath: "http-path", + http.ContextKeyRequestHost: "http-host", + http.ContextKeyRequestXRequestID: "http-x-request-id", + http.ContextKeyRequestRemoteAddr: "http-remote-addr", + http.ContextKeyRequestXForwardedFor: "http-x-forwarded-for", + http.ContextKeyRequestUserAgent: "http-user-agent", + } + for k, v := range keys { + if val, ok := ctx.Value(k).(string); ok && val != "" { + l = log.With(l, v, val) + } + } + // for gRPC requests + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return l + } + for k, v := range md { + l = log.With(l, k, v) + } + return l +} + +// LogMsgWithFields will start with LoggerWithFields and then +// log the given message under the key "msg". +func LogMsgWithFields(ctx context.Context, msg string) error { + return LoggerWithFields(ctx).Log("msg", msg) +} + +// LogMsg will log the given message to the server logger +// with the key "msg". +func LogMsg(ctx context.Context, msg string) error { + return Logger(ctx).Log("msg", msg) +} + +// LogErrorMsgWithFields will start with LoggerWithFields and then log +// the given error under the key "error" and the given message under the key "msg". +func LogErrorMsgWithFields(ctx context.Context, err error, msg string) error { + return LoggerWithFields(ctx).Log("error", err, "msg", msg) +} + +// LogErrorMsg will log the given error under the key "error" and the given message under +// the key "msg". +func LogErrorMsg(ctx context.Context, err error, msg string) error { + return Logger(ctx).Log("error", err, "msg", msg) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/response.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/response.go new file mode 100644 index 000000000..00e1d7d19 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/response.go @@ -0,0 +1,37 @@ +package kit + +import ( + "encoding/json" + "net/http" +) + +// NewJSONStatusResponse allows users to respond with a specific HTTP status code and +// a JSON serialized response. +func NewJSONStatusResponse(res interface{}, code int) *JSONStatusResponse { + return &JSONStatusResponse{res: res, code: code} +} + +// JSONStatusResponse implements: +// `httptransport.StatusCoder` to allow users to respond with the given +// response with a non-200 status code. +// `json.Marshaler` so it can wrap JSON Endpoint responses. +// `error` so it can be used to respond as an error within the go-kit stack. +type JSONStatusResponse struct { + code int + res interface{} +} + +// StatusCode is to implement httptransport.StatusCoder +func (c *JSONStatusResponse) StatusCode() int { + return c.code +} + +// MarshalJSON is to implement json.Marshaler +func (c *JSONStatusResponse) MarshalJSON() ([]byte, error) { + return json.Marshal(c.res) +} + +// Error is to implement error +func (c *JSONStatusResponse) Error() string { + return http.StatusText(c.code) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/router.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/router.go new file mode 100644 index 000000000..c1082677c --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/router.go @@ -0,0 +1,182 @@ +package kit + +import ( + "context" + "net/http" + + "github.com/gorilla/mux" + "github.com/julienschmidt/httprouter" +) + +// Router is an interface to wrap different router implementations. +type Router interface { + Handle(method string, path string, handler http.Handler) + HandleFunc(method string, path string, handlerFunc func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) + SetNotFoundHandler(handler http.Handler) +} + +// RouterOption sets optional Router overrides. +type RouterOption func(Router) Router + +// RouterSelect allows users to override the +// default use of the Gorilla Router. +// TODO add type and constants for names + docs +func RouterSelect(name string) RouterOption { + return func(Router) Router { + switch name { + case "fast", "httprouter": + return &fastRouter{httprouter.New()} + case "gorilla": + return &gorillaRouter{mux.NewRouter()} + case "stdlib": + return &stdlibRouter{http.NewServeMux()} + default: + return &gorillaRouter{mux.NewRouter()} + } + } +} + +// CustomRouter allows users to inject an alternate Router implementation. +func CustomRouter(r Router) RouterOption { + return func(Router) Router { + return r + } +} + +// RouterNotFound will set the not found handler of the router. +func RouterNotFound(h http.Handler) RouterOption { + return func(r Router) Router { + r.SetNotFoundHandler(h) + return r + } +} + +// StdlibRouter is a Router implementation for the Stdlib's `http.ServeMux`. +type stdlibRouter struct { + mux *http.ServeMux +} + +// Handle will call the Stdlib's HandleFunc() methods with a check for the incoming +// HTTP method. To allow for multiple methods on a single route, use 'ANY'. +func (g *stdlibRouter) Handle(method, path string, h http.Handler) { + g.mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { + if r.Method == method || method == "ANY" { + h.ServeHTTP(w, r) + return + } + http.NotFound(w, r) + }) +} + +// HandleFunc will call the Stdlib's HandleFunc() methods with a check for the incoming +// HTTP method. To allow for multiple methods on a single route, use 'ANY'. +func (g *stdlibRouter) HandleFunc(method, path string, h func(http.ResponseWriter, *http.Request)) { + g.Handle(method, path, http.HandlerFunc(h)) +} + +// SetNotFoundHandler will do nothing as we cannot override the stdlib not found. +func (g *stdlibRouter) SetNotFoundHandler(h http.Handler) { +} + +// ServeHTTP will call Stdlib's ServeMux.ServerHTTP directly. +func (g *stdlibRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + g.mux.ServeHTTP(w, r) +} + +// GorillaRouter is a Router implementation for the Gorilla web toolkit's `mux.Router`. +type gorillaRouter struct { + mux *mux.Router +} + +// Handle will call the Gorilla web toolkit's Handle().Method() methods. +func (g *gorillaRouter) Handle(method, path string, h http.Handler) { + g.mux.Handle(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // copy the route params into a shared location + // duplicating memory, but allowing Gizmo to be more flexible with + // router implementations. + r = SetRouteVars(r, mux.Vars(r)) + h.ServeHTTP(w, r) + })).Methods(method) +} + +// HandleFunc will call the Gorilla web toolkit's HandleFunc().Method() methods. +func (g *gorillaRouter) HandleFunc(method, path string, h func(http.ResponseWriter, *http.Request)) { + g.Handle(method, path, http.HandlerFunc(h)) +} + +// SetNotFoundHandler will set the Gorilla mux.Router.NotFoundHandler. +func (g *gorillaRouter) SetNotFoundHandler(h http.Handler) { + g.mux.NotFoundHandler = h +} + +// ServeHTTP will call Gorilla mux.Router.ServerHTTP directly. +func (g *gorillaRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + g.mux.ServeHTTP(w, r) +} + +// FastRouter is a Router implementation for `julienschmidt/httprouter`. +type fastRouter struct { + mux *httprouter.Router +} + +// Handle will call the `httprouter.METHOD` methods and use the HTTPToFastRoute +// to pass httprouter.Params into a Gorilla request context. The params will be available +// via the `FastRouterVars` function. +func (f *fastRouter) Handle(method, path string, h http.Handler) { + f.mux.Handle(method, path, httpToFastRoute(h)) +} + +// HandleFunc will call the `httprouter.METHOD` methods and use the HTTPToFastRoute +// to pass httprouter.Params into a Gorilla request context. The params will be available +// via the `FastRouterVars` function. +func (f *fastRouter) HandleFunc(method, path string, h func(http.ResponseWriter, *http.Request)) { + f.Handle(method, path, http.HandlerFunc(h)) +} + +// SetNotFoundHandler will set httprouter.Router.NotFound. +func (f *fastRouter) SetNotFoundHandler(h http.Handler) { + f.mux.NotFound = h +} + +// ServeHTTP will call httprouter.ServerHTTP directly. +func (f *fastRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + f.mux.ServeHTTP(w, r) +} + +// httpToFastRoute will convert an http.Handler to a httprouter.Handle +// by stuffing any route parameters into a Gorilla request context. +// To access the request parameters within the endpoint, +// use the `Vars` function. +func httpToFastRoute(fh http.Handler) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + if len(params) > 0 { + vars := map[string]string{} + for _, param := range params { + vars[param.Key] = param.Value + } + r = SetRouteVars(r, vars) + } + fh.ServeHTTP(w, r) + } +} + +// Vars is a helper function for accessing route +// parameters from any server.Router implementation. This is the equivalent +// of using `mux.Vars(r)` with the Gorilla mux.Router. +func Vars(r *http.Request) map[string]string { + if rv := r.Context().Value(varsKey); rv != nil { + vars, _ := rv.(map[string]string) + return vars + } + return nil +} + +// SetRouteVars will set the given value into into the request context +// with the shared 'vars' storage key. +func SetRouteVars(r *http.Request, val interface{}) *http.Request { + if val != nil { + r = r.WithContext(context.WithValue(r.Context(), varsKey, val)) + } + return r +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server.go new file mode 100644 index 000000000..7bd143a84 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server.go @@ -0,0 +1,29 @@ +// +build !appengine + +package kit + +import ( + "os" + "os/signal" + "syscall" +) + +// TODO(jprobinson): built in stackdriver error reporting +// TODO(jprobinson): built in stackdriver tracing (sampling) + +// Run will use environment variables to configure the server then register the given +// Service and start up the server(s). +// This will block until the server shuts down. +func Run(service Service) error { + svr := NewServer(service) + + if err := svr.start(); err != nil { + return err + } + + // parse address for host, port + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL) + svr.logger.Log("received signal", <-ch) + return svr.stop() +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server_gae.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server_gae.go new file mode 100644 index 000000000..74bfcfaea --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/server_gae.go @@ -0,0 +1,11 @@ +// +build appengine + +package kit + +import "net/http" + +// Run will not actually start a server if in the App Engine environment. +func Run(service Service) error { + http.Handle("/", NewServer(service)) + return nil +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/service.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/service.go new file mode 100644 index 000000000..1137b46a9 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/kit/service.go @@ -0,0 +1,93 @@ +package kit + +import ( + "net/http" + + "google.golang.org/grpc" + + "github.com/go-kit/kit/endpoint" + httptransport "github.com/go-kit/kit/transport/http" +) + +// HTTPEndpoint encapsulates everything required to build +// an endpoint hosted on a kit server. +type HTTPEndpoint struct { + Endpoint endpoint.Endpoint + Decoder httptransport.DecodeRequestFunc + Encoder httptransport.EncodeResponseFunc + Options []httptransport.ServerOption +} + +// Service is the interface of mixed HTTP/gRPC that can be registered and +// hosted by a gizmo/server/kit server. Services provide hooks for service-wide options +// and middlewares and can be used as a means of dependency injection. +// In general, a Service should just contain the logic for deserializing and authorizing +// requests, passing the request to a business logic interface abstraction, +// handling errors and serializing the apprioriate response. +// +// In other words, each Endpoint is similar to a 'controller' and the Service +// a container for injecting depedencies (business services, repositories, etc.) +// into each request handler. +type Service interface { + // Middleware is for any service-wide go-kit middlewares. This middleware + // is applied to both HTTP and gRPC services. + Middleware(endpoint.Endpoint) endpoint.Endpoint + + // HTTPMiddleware is for service-wide http specific middleware + // for easy integration with 3rd party http.Handlers like nytimes/gziphandler. + HTTPMiddleware(http.Handler) http.Handler + + // HTTPOptions are service-wide go-kit HTTP server options + HTTPOptions() []httptransport.ServerOption + + // HTTPRouterOptions allows users to override the default + // behavior and use of the GorillaRouter. + HTTPRouterOptions() []RouterOption + + // HTTPEndpoints default to using a JSON serializer if no encoder is provided. + // For example: + // + // return map[string]map[string]kit.HTTPEndpoint{ + // "/cat/{id}": { + // "GET": { + // Endpoint: s.GetCatByID, + // Decoder: decodeGetCatRequest, + // }, + // }, + // "/cats": { + // "PUT": { + // Endpoint: s.PutCats, + // HTTPDecoder: decodePutCatsProtoRequest, + // }, + // "GET": { + // Endpoint: s.GetCats, + // HTTPDecoder: decodeGetCatsRequest, + // }, + // }, + // } + HTTPEndpoints() map[string]map[string]HTTPEndpoint + + // RPCMiddleware is for any service-wide gRPC specific middleware + // for easy integration with 3rd party grpc.UnaryServerInterceptors like + // http://godoc.org/cloud.google.com/go/trace#Client.GRPCServerInterceptor + // + // The underlying kit server already uses the one available grpc.UnaryInterceptor + // grpc.ServerOption so attempting to pass your own in this Service's RPCOptions() + // will cause a panic at startup. + // + // If you want to apply multiple RPC middlewares, + // we recommend using: + // http://godoc.org/github.com/grpc-ecosystem/go-grpc-middleware#ChainUnaryServer + RPCMiddleware() grpc.UnaryServerInterceptor + + // RPCServiceDesc allows services to declare an alternate gRPC + // representation of themselves to be hosted on the RPC_PORT (8081 by default). + RPCServiceDesc() *grpc.ServiceDesc + + // RPCOptions are for service-wide gRPC server options. + // + // The underlying kit server already uses the one available grpc.UnaryInterceptor + // grpc.ServerOption so attempting to pass your own in this method will cause a panic + // at startup. We recommend using RPCMiddleware() to fill this need. + RPCOptions() []grpc.ServerOption +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics.go new file mode 100644 index 000000000..fd201e076 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics.go @@ -0,0 +1,157 @@ +package server + +import ( + "bufio" + "errors" + "expvar" + "fmt" + "net" + "net/http" + "strings" + "time" + + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/provider" + "github.com/prometheus/client_golang/prometheus" +) + +func expvarHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") + first := true + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} + +// metricsResponseWriter grabs the StatusCode. +type metricsResponseWriter struct { + w http.ResponseWriter + StatusCode int +} + +func newMetricsResponseWriter(w http.ResponseWriter) *metricsResponseWriter { + return &metricsResponseWriter{w: w} +} + +func (w *metricsResponseWriter) Write(b []byte) (int, error) { + return w.w.Write(b) +} + +func (w *metricsResponseWriter) Header() http.Header { + return w.w.Header() +} + +func (w *metricsResponseWriter) WriteHeader(h int) { + w.StatusCode = h + w.w.WriteHeader(h) +} + +func (w *metricsResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + h, ok := w.w.(http.Hijacker) + if !ok { + return nil, nil, errors.New("response writer does not implement hijacker") + } + return h.Hijack() +} + +// CounterByStatusXX is an http.Handler that counts responses by the first +// digit of their HTTP status code via go-kit/kit/metrics. +type CounterByStatusXX struct { + counter1xx, counter2xx, counter3xx, counter4xx, counter5xx metrics.Counter + handler http.Handler +} + +// CountedByStatusXX returns an http.Handler that passes requests to an +// underlying http.Handler and then counts the response by the first digit of +// its HTTP status code via go-kit/kit/metrics. +func CountedByStatusXX(handler http.Handler, name string, p provider.Provider) *CounterByStatusXX { + return &CounterByStatusXX{ + counter1xx: p.NewCounter(fmt.Sprintf("%s-1xx", name)), + counter2xx: p.NewCounter(fmt.Sprintf("%s-2xx", name)), + counter3xx: p.NewCounter(fmt.Sprintf("%s-3xx", name)), + counter4xx: p.NewCounter(fmt.Sprintf("%s-4xx", name)), + counter5xx: p.NewCounter(fmt.Sprintf("%s-5xx", name)), + handler: handler, + } +} + +// ServeHTTP passes the request to the underlying http.Handler and then counts +// the response by its HTTP status code via go-kit/kit/metrics. +func (c *CounterByStatusXX) ServeHTTP(w0 http.ResponseWriter, r *http.Request) { + w := newMetricsResponseWriter(w0) + c.handler.ServeHTTP(w, r) + if w.StatusCode < 200 { + c.counter1xx.Add(1) + } else if w.StatusCode < 300 { + c.counter2xx.Add(1) + } else if w.StatusCode < 400 { + c.counter3xx.Add(1) + } else if w.StatusCode < 500 { + c.counter4xx.Add(1) + } else { + c.counter5xx.Add(1) + } +} + +// Timer is an http.Handler that counts requests via go-kit/kit/metrics. +type Timer struct { + metrics.Histogram + isProm bool + handler http.Handler +} + +// Timed returns an http.Handler that starts a timer, passes requests to an +// underlying http.Handler, stops the timer, and updates the timer via +// go-kit/kit/metrics. +func Timed(handler http.Handler, name string, p provider.Provider) *Timer { + return &Timer{ + Histogram: p.NewHistogram(name, 50), + handler: handler, + } +} + +// ServeHTTP starts a timer, passes the request to the underlying http.Handler, +// stops the timer, and updates the timer via go-kit/kit/metrics. +func (t *Timer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !t.isProm { + defer func(start time.Time) { + t.Observe(time.Since(start).Seconds()) + }(time.Now()) + } + t.handler.ServeHTTP(w, r) +} + +func metricName(fullPath, method string) string { + // replace slashes + fullPath = strings.Replace(fullPath, "/", "-", -1) + // replace periods + fullPath = strings.Replace(fullPath, ".", "-", -1) + return fmt.Sprintf("routes.%s-%s", fullPath, method) +} + +// TimedAndCounted wraps a http.Handler with Timed and a CountedByStatusXXX or, if the +// metrics provider is of type Prometheus, via a prometheus.InstrumentHandler +func TimedAndCounted(handler http.Handler, fullPath string, method string, p provider.Provider) *Timer { + fullPath = strings.TrimPrefix(fullPath, "/") + switch fmt.Sprintf("%T", p) { + case "*provider.prometheusProvider": + return PrometheusTimedAndCounted(handler, fullPath) + default: + mn := metricName(fullPath, method) + return Timed(CountedByStatusXX(handler, mn+".STATUS-COUNT", p), mn+".DURATION", p) + } +} + +// PrometheusTimedAndCounted wraps a http.Handler with via prometheus.InstrumentHandler +func PrometheusTimedAndCounted(handler http.Handler, name string) *Timer { + return &Timer{ + isProm: true, + handler: prometheus.InstrumentHandler(name, handler), + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics_test.go new file mode 100644 index 000000000..d872b7913 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/metrics_test.go @@ -0,0 +1,187 @@ +package server + +import ( + "bytes" + "io/ioutil" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/go-kit/kit/metrics" +) + +func TestCounterByStatusXX(t *testing.T) { + tests := []int{111, 222, 333, 444, 555} + statuses := make(chan int, 1) + provider := newMockProvider() + + counter := CountedByStatusXX(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + status := <-statuses + w.WriteHeader(status) + if bod, _ := ioutil.ReadAll(r.Body); string(bod) != "blah" { + t.Errorf("CountedByStatusXX expected the request body to be 'blah', got '%s'", string(bod)) + } + r.Body.Close() + }), "counted", provider) + + for _, given := range tests { + statuses <- given + w := httptest.NewRecorder() + r, _ := http.NewRequest("POST", "http://yup.com/foo", bytes.NewBufferString("blah")) + counter.ServeHTTP(w, r) + if given != w.Code { + t.Errorf("CountedByStatusXX expected response code of %d, got %d", given, w.Code) + } + } + + close(statuses) + + if cnt := provider.counters["counted-1xx"].lastAdd; cnt != 1 { + t.Errorf("CountedByStatusXX expected 1xx counter to have a count of 1, got %f", cnt) + } + if cnt := provider.counters["counted-2xx"].lastAdd; cnt != 1 { + t.Errorf("CountedByStatusXX expected 2xx counter to have a count of 1, got %f", cnt) + } + if cnt := provider.counters["counted-3xx"].lastAdd; cnt != 1 { + t.Errorf("CountedByStatusXX expected 3xx counter to have a count of 1, got %f", cnt) + } + if cnt := provider.counters["counted-4xx"].lastAdd; cnt != 1 { + t.Errorf("CountedByStatusXX expected 4xx counter to have a count of 1, got %f", cnt) + } + if cnt := provider.counters["counted-5xx"].lastAdd; cnt != 1 { + t.Errorf("CountedByStatusXX expected 5xx counter to have a count of 1, got %f", cnt) + } +} + +func TestTimer(t *testing.T) { + provider := newMockProvider() + + r, _ := http.NewRequest("POST", "http://uhhuh.io/", bytes.NewBufferString("yerp")) + timer := Timed(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(200 * time.Millisecond) + w.WriteHeader(http.StatusOK) + if bod, _ := ioutil.ReadAll(r.Body); string(bod) != "yerp" { + t.Errorf("Timer expected the request body to be 'yerp', got '%s'", string(bod)) + } + r.Body.Close() + }), "timed", provider) + w := httptest.NewRecorder() + timer.ServeHTTP(w, r) + + if dur := provider.histograms["timed"].lastObserved; dur < 0.2 || dur > 0.3 { + t.Errorf("Timer expected Max() to return between 200 and 300 ms, got %f", dur) + } +} + +func newMockProvider() *mockProvider { + return &mockProvider{ + counters: map[string]*mockCounter{}, + histograms: map[string]*mockHistogram{}, + gauges: map[string]*mockGauge{}, + } +} + +type mockProvider struct { + mtx sync.Mutex + counters map[string]*mockCounter + gauges map[string]*mockGauge + histograms map[string]*mockHistogram +} + +func (p *mockProvider) NewCounter(name string) metrics.Counter { + p.mtx.Lock() + defer p.mtx.Unlock() + c := &mockCounter{} + p.counters[name] = c + return c +} + +func (p *mockProvider) NewHistogram(name string, buckets int) metrics.Histogram { + p.mtx.Lock() + defer p.mtx.Unlock() + h := &mockHistogram{} + p.histograms[name] = h + return h +} + +func (p *mockProvider) NewGauge(name string) metrics.Gauge { + p.mtx.Lock() + defer p.mtx.Unlock() + g := &mockGauge{} + p.gauges[name] = g + return g +} + +func (p *mockProvider) Stop() { +} + +func (c *mockCounter) Name() string { + panic("not implemented") +} + +type mockCounter struct { + mtx sync.Mutex + lastAdd float64 +} + +func (c *mockCounter) With(labelValues ...string) metrics.Counter { + panic("not implemented") +} + +func (c *mockCounter) Add(delta float64) { + c.mtx.Lock() + defer c.mtx.Unlock() + c.lastAdd = delta +} + +type mockGauge struct { + mtx sync.Mutex + lastSet float64 + lastDelta float64 +} + +func (g *mockGauge) Name() string { + panic("not implemented") +} + +func (g *mockGauge) With(labelValues ...string) metrics.Gauge { + panic("not implemented") +} + +func (g *mockGauge) Set(value float64) { + g.mtx.Lock() + defer g.mtx.Unlock() + g.lastSet = value +} + +func (g *mockGauge) Add(delta float64) { + g.mtx.Lock() + defer g.mtx.Unlock() + g.lastDelta = delta + +} + +func (g *mockGauge) Get() float64 { + panic("not implemented") +} + +type mockHistogram struct { + mtx sync.Mutex + lastObserved float64 +} + +func (h *mockHistogram) Name() string { + panic("not implemented") +} + +func (h *mockHistogram) With(labelValues ...string) metrics.Histogram { + panic("not implemented") +} + +func (h *mockHistogram) Observe(value float64) { + h.mtx.Lock() + defer h.mtx.Unlock() + h.lastObserved = value +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware.go new file mode 100644 index 000000000..c94f2d343 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware.go @@ -0,0 +1,125 @@ +package server + +import ( + "bytes" + "encoding/json" + "net/http" + "strings" +) + +// JSONToHTTP is the middleware func to convert a JSONEndpoint to +// an http.HandlerFunc. +func JSONToHTTP(ep JSONEndpoint) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + Log.Warn("unable to close request body: ", err) + } + }() + } + // it's JSON, so always set that content type + w.Header().Set("Content-Type", jsonContentType) + // prepare to grab the response from the ep + var b bytes.Buffer + encoder := json.NewEncoder(&b) + + // call the func and return err or not + code, res, err := ep(r) + w.WriteHeader(code) + if err != nil { + res = err + } + + err = encoder.Encode(res) + if err != nil { + LogWithFields(r).Error("unable to JSON encode response: ", err) + } + + if _, err := w.Write(b.Bytes()); err != nil { + LogWithFields(r).Warn("unable to write response: ", err) + } + }) +} + +// CORSHandler is a middleware func for setting all headers that enable CORS. +// If an originSuffix is provided, a strings.HasSuffix check will be performed +// before adding any CORS header. If an empty string is provided, any Origin +// header found will be placed into the CORS header. If no Origin header is +// found, no headers will be added. +func CORSHandler(f http.Handler, originSuffix string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + origin := r.Header.Get("Origin") + if origin != "" && + (originSuffix == "" || strings.HasSuffix(origin, originSuffix)) { + w.Header().Set("Access-Control-Allow-Origin", origin) + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, x-requested-by, *") + w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, OPTIONS") + } + f.ServeHTTP(w, r) + }) +} + +// NoCacheHandler is a middleware func for setting the Cache-Control to no-cache. +func NoCacheHandler(f http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + f.ServeHTTP(w, r) + }) +} + +// JSONPHandler is a middleware func for wrapping response body with JSONP. +func JSONPHandler(f http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // using a custom ResponseWriter so we can + // capture the response of the main request, + // wrap our JSONP stuff around it + // and only write to the actual response once + jw := &jsonpResponseWriter{w: w} + f.ServeHTTP(jw, r) + + // add the JSONP only if the callback exists + callbackLabel := r.FormValue("callback") + if callbackLabel != "" { + var result []byte + result = append(jsonpStart, []byte(callbackLabel)...) + result = append(result, jsonpSecond...) + result = append(result, jw.buf.Bytes()...) + result = append(result, jsonpEnd...) + if _, err := w.Write(result); err != nil { + LogWithFields(r).Warn("unable to write JSONP response: ", err) + } + } else { + // if no callback, just write the bytes + if _, err := w.Write(jw.buf.Bytes()); err != nil { + LogWithFields(r).Warn("unable to write response: ", err) + } + } + }) +} + +var ( + jsonpStart = []byte("/**/") + jsonpSecond = []byte("(") + jsonpEnd = []byte(");") +) + +type jsonpResponseWriter struct { + w http.ResponseWriter + buf bytes.Buffer +} + +func (w *jsonpResponseWriter) Header() http.Header { + return w.w.Header() +} + +func (w *jsonpResponseWriter) WriteHeader(h int) { + w.w.WriteHeader(h) +} + +func (w *jsonpResponseWriter) Write(b []byte) (int, error) { + return w.buf.Write(b) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_go17.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_go17.go new file mode 100644 index 000000000..59f1324d5 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_go17.go @@ -0,0 +1,74 @@ +// +build go1.7 + +package server + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +// JSONContextToHTTP is a middleware func to convert a ContextHandler an http.Handler. +func JSONContextToHTTP(ep JSONContextEndpoint) ContextHandler { + return ContextHandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + } + }() + } + // it's JSON, so always set that content type + w.Header().Set("Content-Type", jsonContentType) + // prepare to grab the response from the ep + var b bytes.Buffer + encoder := json.NewEncoder(&b) + + // call the func and return err or not + code, res, err := ep(ctx, r) + w.WriteHeader(code) + if err != nil { + res = err + } + + err = encoder.Encode(res) + if err != nil { + LogWithFields(r).Error("unable to JSON encode response: ", err) + } + + if _, err := w.Write(b.Bytes()); err != nil { + LogWithFields(r).Warn("unable to write response: ", err) + } + }) +} + +// ContextToHTTP is a middleware func to convert a ContextHandler an http.Handler. +func ContextToHTTP(ep ContextHandler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ep.ServeHTTPContext(r.Context(), w, r) + }) +} + +// WithCloseHandler returns a Handler cancelling the context when the client +// connection close unexpectedly. +func WithCloseHandler(h ContextHandler) ContextHandler { + return ContextHandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + // Cancel the context if the client closes the connection + if wcn, ok := w.(http.CloseNotifier); ok { + var cancel context.CancelFunc + ctx, cancel = context.WithCancel(ctx) + defer cancel() + + notify := wcn.CloseNotify() + go func() { + select { + case <-notify: + cancel() + case <-ctx.Done(): + } + }() + } + + h.ServeHTTPContext(ctx, w, r) + }) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_pre_go17.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_pre_go17.go new file mode 100644 index 000000000..d5fe9db80 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_pre_go17.go @@ -0,0 +1,75 @@ +// +build !go1.7 + +package server + +import ( + "bytes" + "encoding/json" + "net/http" + + "golang.org/x/net/context" +) + +// JSONContextToHTTP is a middleware func to convert a ContextHandler an http.Handler. +func JSONContextToHTTP(ep JSONContextEndpoint) ContextHandler { + return ContextHandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + } + }() + } + // it's JSON, so always set that content type + w.Header().Set("Content-Type", jsonContentType) + // prepare to grab the response from the ep + var b bytes.Buffer + encoder := json.NewEncoder(&b) + + // call the func and return err or not + code, res, err := ep(ctx, r) + w.WriteHeader(code) + if err != nil { + res = err + } + + err = encoder.Encode(res) + if err != nil { + LogWithFields(r).Error("unable to JSON encode response: ", err) + } + + if _, err := w.Write(b.Bytes()); err != nil { + LogWithFields(r).Warn("unable to write response: ", err) + } + }) +} + +// ContextToHTTP is a middleware func to convert a ContextHandler an http.Handler. +func ContextToHTTP(ep ContextHandler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ep.ServeHTTPContext(context.Background(), w, r) + }) +} + +// WithCloseHandler returns a Handler cancelling the context when the client +// connection close unexpectedly. +func WithCloseHandler(h ContextHandler) ContextHandler { + return ContextHandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) { + // Cancel the context if the client closes the connection + if wcn, ok := w.(http.CloseNotifier); ok { + var cancel context.CancelFunc + ctx, cancel = context.WithCancel(ctx) + defer cancel() + + notify := wcn.CloseNotify() + go func() { + select { + case <-notify: + cancel() + case <-ctx.Done(): + } + }() + } + + h.ServeHTTPContext(ctx, w, r) + }) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_test.go new file mode 100644 index 000000000..5aea90bfa --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/middleware_test.go @@ -0,0 +1,179 @@ +package server + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +func TestCORSHandler(t *testing.T) { + tests := []struct { + given string + givenPrefix string + + wantOrigin string + wantCreds string + wantHeaders string + wantMethods string + }{ + { + "", + "", + "", + "", + "", + "", + }, + { + ".nytimes.com.", + "", + ".nytimes.com.", + "true", + "Content-Type, x-requested-by, *", + "GET, PUT, POST, DELETE, OPTIONS", + }, + { + ".nytimes.com.", + "blah.com", + "", + "", + "", + "", + }, + } + + for _, test := range tests { + r, _ := http.NewRequest("GET", "", nil) + r.Header.Add("Origin", test.given) + w := httptest.NewRecorder() + + CORSHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }), test.givenPrefix).ServeHTTP(w, r) + + if got := w.Header().Get("Access-Control-Allow-Origin"); got != test.wantOrigin { + t.Errorf("expected CORS origin header to be '%#v', got '%#v'", test.wantOrigin, got) + } + if got := w.Header().Get("Access-Control-Allow-Credentials"); got != test.wantCreds { + t.Errorf("expected CORS creds header to be '%#v', got '%#v'", test.wantCreds, got) + } + if got := w.Header().Get("Access-Control-Allow-Headers"); got != test.wantHeaders { + t.Errorf("expected CORS 'headers' header to be '%#v', got '%#v'", test.wantHeaders, got) + } + if got := w.Header().Get("Access-Control-Allow-Methods"); got != test.wantMethods { + t.Errorf("expected CORS 'methods' header to be '%#v', got '%#v'", test.wantMethods, got) + } + } +} + +func TestJSONToHTTP(t *testing.T) { + tests := []struct { + given JSONEndpoint + givenBody io.Reader + + wantCode int + wantBody string + }{ + { + JSONEndpoint(func(r *http.Request) (int, interface{}, error) { + bod, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Error("unable to read given request body: ", err) + } + if string(bod) != "yup" { + t.Errorf("expected 'yup', got %+v", string(bod)) + } + return http.StatusOK, struct{ Howdy string }{"Hi"}, nil + }), + bytes.NewBufferString("yup"), + http.StatusOK, + "{\"Howdy\":\"Hi\"}\n", + }, + { + JSONEndpoint(func(r *http.Request) (int, interface{}, error) { + return http.StatusServiceUnavailable, nil, &testJSONError{"nope"} + }), + nil, + http.StatusServiceUnavailable, + "{\"error\":\"nope\"}\n", + }, + } + + for _, test := range tests { + r, _ := http.NewRequest("GET", "", test.givenBody) + w := httptest.NewRecorder() + JSONToHTTP(test.given).ServeHTTP(w, r) + + if w.Code != test.wantCode { + t.Errorf("expected status code %d, got %d", test.wantCode, w.Code) + } + if gotHdr := w.Header().Get("Content-Type"); gotHdr != jsonContentType { + t.Errorf("expected Content-Type header of '%#v', got '%#v'", jsonContentType, gotHdr) + } + if got := w.Body.String(); got != test.wantBody { + t.Errorf("expected body of '%#v', got '%#v'", test.wantBody, got) + } + } +} + +type testJSONError struct { + Err string `json:"error"` +} + +func (t *testJSONError) Error() string { + return t.Err +} + +func TestJSONPHandler(t *testing.T) { + r, _ := http.NewRequest("GET", "", nil) + r.Form = url.Values{"callback": {"harumph"}} + w := httptest.NewRecorder() + + JSONPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("{\"jsonp\":\"sucks\"}")) + })).ServeHTTP(w, r) + + want := `/**/harumph({"jsonp":"sucks"});` + if got := w.Body.String(); got != want { + t.Errorf("expected JSONP response of '%#v', got '%#v'", want, got) + } + + // once again, without a callback + r, _ = http.NewRequest("GET", "", nil) + w = httptest.NewRecorder() + + JSONPHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("{\"jsonp\":\"sucks\"}")) + })).ServeHTTP(w, r) + + want = `{"jsonp":"sucks"}` + if got := w.Body.String(); got != want { + t.Errorf("expected JSONP response of '%#v', got '%#v'", want, got) + } +} + +func TestNoCacheHandler(t *testing.T) { + r, _ := http.NewRequest("GET", "", nil) + w := httptest.NewRecorder() + + NoCacheHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })).ServeHTTP(w, r) + + want := "no-cache, no-store, must-revalidate" + if got := w.Header().Get("Cache-Control"); got != want { + t.Errorf("expected no-cache control header to be '%#v', got '%#v'", want, got) + } + want = "no-cache" + if got := w.Header().Get("Pragma"); got != want { + t.Errorf("expected no-cache pragma header to be '%#v', got '%#v'", want, got) + } + want = "0" + if got := w.Header().Get("Expires"); got != want { + t.Errorf("expected no-cache Expires header to be '%#v', got '%#v'", want, got) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router.go new file mode 100644 index 000000000..efed9ed30 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router.go @@ -0,0 +1,110 @@ +package server + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/julienschmidt/httprouter" + + "github.com/NYTimes/gizmo/web" +) + +// Router is an interface to wrap different router types to be embedded within +// Gizmo server.Server implementations. +type Router interface { + Handle(method string, path string, handler http.Handler) + HandleFunc(method string, path string, handlerFunc func(http.ResponseWriter, *http.Request)) + ServeHTTP(w http.ResponseWriter, r *http.Request) + SetNotFoundHandler(handler http.Handler) +} + +// NewRouter will return the router specified by the server +// config. If no Router value is supplied, the server +// will default to using Gorilla mux. +func NewRouter(cfg *Config) Router { + switch cfg.RouterType { + case "fast", "httprouter": + return &FastRouter{httprouter.New()} + case "gorilla": + return &GorillaRouter{mux.NewRouter()} + default: + return &GorillaRouter{mux.NewRouter()} + } +} + +// GorillaRouter is a Router implementation for the Gorilla web toolkit's `mux.Router`. +type GorillaRouter struct { + mux *mux.Router +} + +// Handle will call the Gorilla web toolkit's Handle().Method() methods. +func (g *GorillaRouter) Handle(method, path string, h http.Handler) { + g.mux.Handle(path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // copy the route params into a shared location + // duplicating memory, but allowing Gizmo to be more flexible with + // router implementations. + web.SetRouteVars(r, mux.Vars(r)) + h.ServeHTTP(w, r) + })).Methods(method) +} + +// HandleFunc will call the Gorilla web toolkit's HandleFunc().Method() methods. +func (g *GorillaRouter) HandleFunc(method, path string, h func(http.ResponseWriter, *http.Request)) { + g.Handle(method, path, http.HandlerFunc(h)) +} + +// SetNotFoundHandler will set the Gorilla mux.Router.NotFoundHandler. +func (g *GorillaRouter) SetNotFoundHandler(h http.Handler) { + g.mux.NotFoundHandler = h +} + +// ServeHTTP will call Gorilla mux.Router.ServerHTTP directly. +func (g *GorillaRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + g.mux.ServeHTTP(w, r) +} + +// FastRouter is a Router implementation for `julienschmidt/httprouter`. +type FastRouter struct { + mux *httprouter.Router +} + +// Handle will call the `httprouter.METHOD` methods and use the HTTPToFastRoute +// to pass httprouter.Params into a Gorilla request context. The params will be available +// via the `FastRouterVars` function. +func (f *FastRouter) Handle(method, path string, h http.Handler) { + f.mux.Handle(method, path, HTTPToFastRoute(h)) +} + +// HandleFunc will call the `httprouter.METHOD` methods and use the HTTPToFastRoute +// to pass httprouter.Params into a Gorilla request context. The params will be available +// via the `FastRouterVars` function. +func (f *FastRouter) HandleFunc(method, path string, h func(http.ResponseWriter, *http.Request)) { + f.Handle(method, path, http.HandlerFunc(h)) +} + +// SetNotFoundHandler will set httprouter.Router.NotFound. +func (f *FastRouter) SetNotFoundHandler(h http.Handler) { + f.mux.NotFound = h +} + +// ServeHTTP will call httprouter.ServerHTTP directly. +func (f *FastRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + f.mux.ServeHTTP(w, r) +} + +// HTTPToFastRoute will convert an http.Handler to a httprouter.Handle +// by stuffing any route parameters into a Gorilla request context. +// To access the request parameters within the endpoint, +// use the `web.Vars` function. +func HTTPToFastRoute(fh http.Handler) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) { + if len(params) > 0 { + vars := map[string]string{} + for _, param := range params { + vars[param.Key] = param.Value + } + web.SetRouteVars(r, vars) + } + fh.ServeHTTP(w, r) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router_test.go new file mode 100644 index 000000000..c4d40b265 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/router_test.go @@ -0,0 +1,51 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestFastRoute(t *testing.T) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/1/{something}/blah", nil) + r.RemoteAddr = "0.0.0.0:8080" + + srvr.ServeHTTP(w, r) + + if w.Code != http.StatusOK { + t.Errorf("SimpleHealthCheck expected 200 response code, got %d", w.Code) + } + + wantBody := "blah" + if gotBody := w.Body.String(); gotBody != wantBody { + t.Errorf("Fast route expected response body to be %q, got %q", wantBody, gotBody) + } +} + +func TestGorillaRoute(t *testing.T) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/1/blah/:something", nil) + r.RemoteAddr = "0.0.0.0:8080" + + srvr.ServeHTTP(w, r) + + if w.Code != http.StatusOK { + t.Errorf("SimpleHealthCheck expected 200 response code, got %d", w.Code) + } + + wantBody := "blah" + if gotBody := w.Body.String(); gotBody != wantBody { + t.Errorf("Fast route expected response body to be %q, got %q", wantBody, gotBody) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/rpc_server.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/rpc_server.go new file mode 100644 index 000000000..14cbe63a1 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/rpc_server.go @@ -0,0 +1,321 @@ +package server + +import ( + "errors" + "fmt" + "net" + "net/http" + "runtime/debug" + "strings" + "time" + + "github.com/NYTimes/logrotate" + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/provider" + + "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// RPCServer is an experimental server that serves a gRPC server on one +// port and the same endpoints via JSON on another port. +type RPCServer struct { + cfg *Config + + // exit chan for graceful shutdown + exit chan chan error + + // server for handling RPC requests + srvr *grpc.Server + + // mux for routing HTTP requests + mux Router + + // tracks active requests + monitor *ActivityMonitor + + mets provider.Provider + panicCounter metrics.Counter +} + +// NewRPCServer will instantiate a new experimental RPCServer with the given config. +func NewRPCServer(cfg *Config) *RPCServer { + if cfg == nil { + cfg = &Config{} + } + mx := NewRouter(cfg) + if cfg.NotFoundHandler != nil { + mx.SetNotFoundHandler(cfg.NotFoundHandler) + } + mets := newMetricsProvider(cfg) + return &RPCServer{ + cfg: cfg, + srvr: grpc.NewServer(), + mux: mx, + exit: make(chan chan error), + monitor: NewActivityMonitor(), + mets: mets, + panicCounter: mets.NewCounter("panic"), + } +} + +// Register will attempt to register the given RPCService with the server. +// If any other types are passed, Register will panic. +func (r *RPCServer) Register(svc Service) error { + rpcsvc, ok := svc.(RPCService) + if !ok { + Log.Fatalf("invalid service type for rpc server: %T", svc) + } + + // register RPC + desc, grpcSvc := rpcsvc.Service() + r.srvr.RegisterService(desc, grpcSvc) + // register endpoints + for _, mthd := range desc.Methods { + registerRPCMetrics(mthd.MethodName, r.mets) + } + + // register HTTP + // loop through json endpoints and register them + prefix := svc.Prefix() + // quick fix for backwards compatibility + prefix = strings.TrimRight(prefix, "/") + + // register all context endpoints with our wrapper + for path, epMethods := range rpcsvc.ContextEndpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + r.mux.Handle(method, prefix+path, TimedAndCounted( + func(ep ContextHandlerFunc, cs ContextService) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + Log.Warn("unable to close request body: ", err) + } + }() + } + rpcsvc.Middleware(ContextToHTTP(rpcsvc.ContextMiddleware(ep))).ServeHTTP(w, r) + }) + }(ep, rpcsvc), + prefix+path, method, r.mets), + ) + } + } + + // register all JSON context endpoints with our wrapper + for path, epMethods := range rpcsvc.JSONEndpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + r.mux.Handle(method, prefix+path, TimedAndCounted( + rpcsvc.Middleware(ContextToHTTP(rpcsvc.ContextMiddleware( + JSONContextToHTTP(rpcsvc.JSONMiddleware(ep)), + ))), + prefix+path, method, r.mets), + ) + } + } + + RegisterProfiler(r.cfg, r.mux) + + return nil +} + +// Start start the RPC server. +func (r *RPCServer) Start() error { + // setup RPC + registerRPCAccessLogger(r.cfg) + rl, err := net.Listen("tcp", fmt.Sprintf(":%d", r.cfg.RPCPort)) + if err != nil { + return err + } + + go func() { + if err := r.srvr.Serve(rl); err != nil { + Log.Error("encountered an error while serving RPC listener: ", err) + } + }() + + Log.Infof("RPC listening on %s", rl.Addr().String()) + + // setup HTTP + healthHandler := RegisterHealthHandler(r.cfg, r.monitor, r.mux) + r.cfg.HealthCheckPath = healthHandler.Path() + + wrappedHandler, err := NewAccessLogMiddleware(r.cfg.RPCAccessLog, r) + if err != nil { + Log.Fatalf("unable to create http access log: %s", err) + } + + srv := httpServer(wrappedHandler) + var hl net.Listener + hl, err = net.Listen("tcp", fmt.Sprintf(":%d", r.cfg.HTTPPort)) + if err != nil { + return err + } + + go func() { + if err := srv.Serve(hl); err != nil { + Log.Error("encountered an error while serving listener: ", err) + } + }() + + Log.Infof("HTTP listening on %s", hl.Addr().String()) + + // join the LB + go func() { + exit := <-r.exit + + if err := healthHandler.Stop(); err != nil { + Log.Warn("health check Stop returned with error: ", err) + } + + r.srvr.Stop() + exit <- hl.Close() + }() + + return nil +} + +// Stop will signal the RPC server to stop and block until it does. +func (r *RPCServer) Stop() error { + ch := make(chan error) + r.exit <- ch + return <-ch +} + +// ServeHTTP is RPCServer's hook for metrics and safely executing each request. +func (r *RPCServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { + AddIPToContext(req) + + // only count non-LB requests + if req.URL.Path != r.cfg.HealthCheckPath { + r.monitor.CountRequest() + defer r.monitor.UncountRequest() + } + + r.safelyExecuteHTTPRequest(w, req) +} + +// executeRequestSafely will prevent a panic in a request from bringing the server down. +func (r *RPCServer) safelyExecuteHTTPRequest(w http.ResponseWriter, req *http.Request) { + defer func() { + if x := recover(); x != nil { + // register a panic'd request with our metrics + r.panicCounter.Add(1) + + // log the panic for all the details later + LogWithFields(req).Errorf("rpc server recovered from an HTTP panic\n%v: %v", x, string(debug.Stack())) + + // give the users our deepest regrets + w.WriteHeader(http.StatusInternalServerError) + if _, err := w.Write(UnexpectedServerError); err != nil { + LogWithFields(req).Warn("unable to write response: ", err) + } + + } + }() + + // hand the request off to gorilla + r.mux.ServeHTTP(w, req) +} + +// LogRPCWithFields will feed any request context into a logrus Entry. +func LogRPCWithFields(ctx context.Context, log *logrus.Logger) *logrus.Entry { + md, ok := metadata.FromContext(ctx) + if !ok { + return logrus.NewEntry(log) + } + return log.WithFields(MetadataToFields(md)) +} + +// MetadataToFields will accept all values from a metadata.MD and +// create logrus.Fields with the same set. +func MetadataToFields(md metadata.MD) logrus.Fields { + f := logrus.Fields{} + for k, v := range md { + f[k] = v + } + return f +} + +// MonitorRPCRequest should be deferred by any RPC method that would like to have +// metrics and access logging, participate in graceful shutdowns and safely recover from panics. +func MonitorRPCRequest() func(ctx context.Context, methodName string, err *error) { + start := time.Now() + return func(ctx context.Context, methodName string, err *error) { + m := rpcEndpointMetrics["rpc."+methodName] + x := recover() + + if x != nil { + // log the panic for all the details later + Log.Warningf("rpc server recovered from a panic\n%v: %v", x, string(debug.Stack())) + + // give the users our deepest regrets + tmp := errors.New(string(UnexpectedServerError)) + err = &tmp + } + if m == nil { + Log.Errorf("unable to monitor rpc request. unknown method name: %s", methodName) + return + } + if x != nil { + // register a panic'd request with our metrics + m.PanicCounter.Add(1) + } + if *err == nil { + m.SuccessCounter.Add(1) + } else { + m.ErrorCounter.Add(1) + } + m.Timer.Observe(time.Since(start).Seconds()) + + if rpcAccessLog != nil { + LogRPCWithFields(ctx, rpcAccessLog).WithFields(logrus.Fields{ + "name": methodName, + "duration": time.Since(start), + "error": err, + }).Info("access") + } + } +} + +var rpcEndpointMetrics = map[string]*rpcMetrics{} + +type rpcMetrics struct { + Timer metrics.Histogram + SuccessCounter metrics.Counter + ErrorCounter metrics.Counter + PanicCounter metrics.Counter +} + +func registerRPCMetrics(name string, mets provider.Provider) { + name = "rpc." + name + rpcEndpointMetrics[name] = &rpcMetrics{ + Timer: mets.NewHistogram(name+".DURATION", 50), + SuccessCounter: mets.NewCounter(name + ".SUCCESS"), + ErrorCounter: mets.NewCounter(name + ".ERROR"), + PanicCounter: mets.NewCounter(name + ".PANIC"), + } +} + +// access logger +var rpcAccessLog *logrus.Logger + +func registerRPCAccessLogger(cfg *Config) { + // gRPC doesn't have a hook à la http.Handler-middleware + // so some of this duplicates logic from config.NewAccessLogMiddleware + if cfg.RPCAccessLog == nil { + return + } + + lf, err := logrotate.NewFile(*cfg.RPCAccessLog) + if err != nil { + Log.Fatalf("unable to access rpc access log file: %s", err) + } + + rpcAccessLog = logrus.New() + rpcAccessLog.Out = lf +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/server.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/server.go new file mode 100644 index 000000000..e81a61ae6 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/server.go @@ -0,0 +1,289 @@ +package server + +import ( + "errors" + "net/http" + "net/http/pprof" + "os" + "os/signal" + "runtime" + "strings" + "syscall" + "time" + + "github.com/sirupsen/logrus" + "github.com/go-kit/kit/metrics/provider" + "github.com/nu7hatch/gouuid" + + "github.com/NYTimes/gizmo/config/metrics" + "github.com/NYTimes/gizmo/web" + "github.com/NYTimes/logrotate" +) + +// Version is meant to be set with the current package version at build time. +var Version string + +// Server is the basic interface that defines what to expect from any server. +type Server interface { + Register(Service) error + Start() error + Stop() error +} + +var ( + // Name is used for status and logging. + Name = "nyt-awesome-go-server" + // Log is the global logger for the server. It will take care of logrotate + // and it can accept 'fields' to include with each log line: see LogWithFields(r). + Log = logrus.New() + // server is what's used in the global server funcs in the package. + server Server + // maxHeaderBytes is used by the http server to limit the size of request headers. + // This may need to be increased if accepting cookies from the public. + maxHeaderBytes = 1 << 20 + // readTimeout is used by the http server to set a maximum duration before + // timing out read of the request. The default timeout is 10 seconds. + readTimeout = 10 * time.Second + // writeTimeout is used by the http server to set a maximum duration before + // timing out write of the response. The default timeout is 10 seconds. + writeTimeout = 10 * time.Second + // jsonContentType is the content type that will be used for JSONEndpoints. + // It will default to the web.JSONContentType value. + jsonContentType = web.JSONContentType + // idleTimeout is used by the http server to set a maximum duration for + // keep-alive connections. + idleTimeout = 120 * time.Second +) + +// Init will set up our name, logging, healthchecks and parse flags. If DefaultServer isn't set, +// this func will set it to a `SimpleServer` listening on `Config.HTTPPort`. +func Init(name string, scfg *Config) { + // generate a unique ID for the server + id, _ := uuid.NewV4() + Name = name + "-" + Version + "-" + id.String() + + // if no config given, attempt to pull one from + // the environment. + if scfg == nil { + // allow the default config to be overridden by CLI + scfg = LoadConfigFromEnv() + SetConfigOverrides(scfg) + } + + if scfg.GOMAXPROCS != nil { + runtime.GOMAXPROCS(*scfg.GOMAXPROCS) + } else { + runtime.GOMAXPROCS(runtime.NumCPU()) + } + + if scfg.JSONContentType != nil { + jsonContentType = *scfg.JSONContentType + } + + if scfg.MaxHeaderBytes != nil { + maxHeaderBytes = *scfg.MaxHeaderBytes + } + + if scfg.ReadTimeout != nil { + tReadTimeout, err := time.ParseDuration(*scfg.ReadTimeout) + if err != nil { + Log.Fatal("invalid server ReadTimeout: ", err) + } + readTimeout = tReadTimeout + } + + if scfg.IdleTimeout != nil { + tIdleTimeout, err := time.ParseDuration(*scfg.IdleTimeout) + if err != nil { + Log.Fatal("invalid server IdleTimeout: ", err) + } + idleTimeout = tIdleTimeout + } + + if scfg.WriteTimeout != nil { + tWriteTimeout, err := time.ParseDuration(*scfg.WriteTimeout) + if err != nil { + Log.Fatal("invalid server WriteTimeout: ", err) + } + writeTimeout = tWriteTimeout + } + + // setup app logging + if scfg.Log != "" { + lf, err := logrotate.NewFile(scfg.Log) + if err != nil { + Log.Fatalf("unable to access log file: %s", err) + } + Log.Out = lf + + // json output when writing to file by default + if scfg.LogJSONFormat == nil { + Log.Formatter = &logrus.JSONFormatter{} + } + + } else { + Log.Out = os.Stderr + } + + // override default JSON settings + if scfg.LogJSONFormat != nil && *scfg.LogJSONFormat { + Log.Formatter = &logrus.JSONFormatter{} + } + + SetLogLevel(scfg) + + server = NewServer(scfg) +} + +// Register will add a new Service to the DefaultServer. +func Register(svc Service) error { + return server.Register(svc) +} + +// Run will start the DefaultServer and set it up to Stop() +// on a kill signal. +func Run() error { + Log.Infof("Starting new %s server", Name) + if err := server.Start(); err != nil { + return err + } + + // parse address for host, port + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT) + Log.Infof("Received signal %s", <-ch) + return Stop() +} + +// Stop will stop the default server. +func Stop() error { + Log.Infof("Stopping %s server", Name) + return server.Stop() +} + +// LogWithFields will feed any request context into a logrus Entry. +func LogWithFields(r *http.Request) *logrus.Entry { + return Log.WithFields(ContextFields(r)) +} + +// NewServer will inspect the config and generate +// the appropriate Server implementation. +func NewServer(cfg *Config) Server { + switch cfg.ServerType { + case "simple": + return NewSimpleServer(cfg) + case "rpc": + return NewRPCServer(cfg) + case "appengine": + cfg.appEngine = true + return NewSimpleServer(cfg) + default: + return NewSimpleServer(cfg) + } +} + +// NewHealthCheckHandler will inspect the config to generate +// the appropriate HealthCheckHandler. +func NewHealthCheckHandler(cfg *Config) (HealthCheckHandler, error) { + // default the status path if not set + if cfg.HealthCheckPath == "" { + cfg.HealthCheckPath = "/status.txt" + } + switch cfg.HealthCheckType { + case "simple": + return NewSimpleHealthCheck(cfg.HealthCheckPath), nil + case "esx": + return NewESXHealthCheck(), nil + case "custom": + if cfg.CustomHealthCheckHandler == nil { + return nil, errors.New("health check type is set to 'custom', but no Config.CustomHealthCheckHandler provided") + } + return NewCustomHealthCheck(cfg.HealthCheckPath, cfg.CustomHealthCheckHandler), nil + default: + return NewSimpleHealthCheck(cfg.HealthCheckPath), nil + } +} + +// RegisterProfiler will add handlers for pprof endpoints if +// the config has them enabled. +func RegisterProfiler(cfg *Config, mx Router) { + if !cfg.EnablePProf { + return + } + mx.HandleFunc("GET", "/debug/pprof/", pprof.Index) + mx.HandleFunc("GET", "/debug/pprof/cmdline", pprof.Cmdline) + mx.HandleFunc("GET", "/debug/pprof/profile", pprof.Profile) + mx.HandleFunc("GET", "/debug/pprof/symbol", pprof.Symbol) + mx.HandleFunc("GET", "/debug/pprof/trace", pprof.Trace) + + // Manually add support for paths linked to by index page at /debug/pprof/ + mx.Handle("GET", "/debug/pprof/goroutine", pprof.Handler("goroutine")) + mx.Handle("GET", "/debug/pprof/heap", pprof.Handler("heap")) + mx.Handle("GET", "/debug/pprof/threadcreate", pprof.Handler("threadcreate")) + mx.Handle("GET", "/debug/pprof/block", pprof.Handler("block")) +} + +// RegisterHealthHandler will create a new HealthCheckHandler from the +// given config and add a handler to the given router. +func RegisterHealthHandler(cfg *Config, monitor *ActivityMonitor, mx Router) HealthCheckHandler { + // register health check + hch, err := NewHealthCheckHandler(cfg) + if err != nil { + Log.Fatal("unable to configure the HealthCheckHandler: ", err) + } + err = hch.Start(monitor) + if err != nil { + Log.Fatal("unable to start the HealthCheckHandler: ", err) + } + mx.Handle("GET", hch.Path(), hch) + mx.Handle("HEAD", hch.Path(), hch) + return hch +} + +// MetricsNamespace returns "apps.{hostname prefix}", which is +// the convention used in NYT ESX environment. +func MetricsNamespace() string { + // get only server base name + name, _ := os.Hostname() + name = strings.SplitN(name, ".", 2)[0] + // set it up to be paperboy.servername + name = strings.Replace(name, "-", ".", 1) + // add the 'apps' prefix to keep things neat + return "apps." + name +} + +func newMetricsProvider(cfg *Config) provider.Provider { + if cfg.MetricsProvider != nil { + return cfg.MetricsProvider + } + // deal with deprecated GRAPHITE_HOST value + if cfg.GraphiteHost != nil { + cfg.Metrics.Type = metrics.Graphite + cfg.Metrics.Addr = *cfg.GraphiteHost + } + // set default metrics prefix + // to MetricsNamespace + if len(cfg.Metrics.Prefix) == 0 { + cfg.Metrics.Prefix = MetricsNamespace() + "." + } + // set default metrics namespace + // to MetricsNamespace + if len(cfg.Metrics.Prefix) == 0 { + cfg.Metrics.Namespace = MetricsNamespace() + "." + } + p := cfg.MetricsProvider + if p == nil { + p = cfg.Metrics.NewProvider() + } + return p +} + +// SetLogLevel will set the appropriate logrus log level +// given the server config. +func SetLogLevel(scfg *Config) { + if lvl, err := logrus.ParseLevel(scfg.LogLevel); err != nil { + Log.Level = logrus.InfoLevel + } else { + Log.Level = lvl + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service.go new file mode 100644 index 000000000..2237e58a6 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service.go @@ -0,0 +1,90 @@ +package server + +import ( + "net/http" + + "google.golang.org/grpc" +) + +// Service is the most basic interface of a service that can be received and +// hosted by a Server. +type Service interface { + Prefix() string + + // Middleware is a hook to enable services to add + // any additional middleware. + Middleware(http.Handler) http.Handler +} + +// SimpleService is an interface defining a service that +// is made up of http.HandlerFuncs. +type SimpleService interface { + Service + + // route - method - func + Endpoints() map[string]map[string]http.HandlerFunc +} + +// JSONService is an interface defining a service that +// is made up of JSONEndpoints. +type JSONService interface { + Service + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + JSONMiddleware(JSONEndpoint) JSONEndpoint +} + +// MixedService is an interface defining service that +// offer JSONEndpoints and simple http.HandlerFunc endpoints. +type MixedService interface { + Service + + // route - method - func + Endpoints() map[string]map[string]http.HandlerFunc + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONEndpoint + JSONMiddleware(JSONEndpoint) JSONEndpoint +} + +// RPCService is an interface defining an grpc-compatible service that +// also offers JSONContextEndpoints and ContextHandlerFuncs. +type RPCService interface { + ContextService + + Service() (*grpc.ServiceDesc, interface{}) + + // Ensure that the route syntax is compatible with the router + // implementation chosen in cfg.RouterType. + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + JSONMiddleware(JSONContextEndpoint) JSONContextEndpoint +} + +// JSONEndpoint is the JSONService equivalent to SimpleService's http.HandlerFunc. +type JSONEndpoint func(*http.Request) (int, interface{}, error) + +// ContextService is an interface defining a service that +// is made up of ContextHandlerFuncs. +type ContextService interface { + Service + + // route - method - func + ContextEndpoints() map[string]map[string]ContextHandlerFunc + ContextMiddleware(ContextHandler) ContextHandler +} + +// MixedContextService is an interface defining a service that +// is made up of JSONContextEndpoints and ContextHandlerFuncs. +type MixedContextService interface { + ContextService + + // route - method - func + JSONEndpoints() map[string]map[string]JSONContextEndpoint + JSONContextMiddleware(JSONContextEndpoint) JSONContextEndpoint +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_go17.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_go17.go new file mode 100644 index 000000000..b4e206926 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_go17.go @@ -0,0 +1,24 @@ +// +build go1.7 + +package server + +import ( + "context" + "net/http" +) + +// JSONContextEndpoint is the JSONContextService equivalent to JSONService's JSONEndpoint. +type JSONContextEndpoint func(context.Context, *http.Request) (int, interface{}, error) + +// ContextHandlerFunc is an equivalent to SimpleService's http.HandlerFunc. +type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request) + +// ServeHTTPContext is an implementation of ContextHandler interface. +func (h ContextHandlerFunc) ServeHTTPContext(ctx context.Context, rw http.ResponseWriter, req *http.Request) { + h(ctx, rw, req) +} + +// ContextHandler is an equivalent to http.Handler but with additional param. +type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_pre_go17.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_pre_go17.go new file mode 100644 index 000000000..4f606ffc1 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/service_pre_go17.go @@ -0,0 +1,25 @@ +// +build !go1.7 + +package server + +import ( + "net/http" + + "golang.org/x/net/context" +) + +// JSONContextEndpoint is the JSONContextService equivalent to JSONService's JSONEndpoint. +type JSONContextEndpoint func(context.Context, *http.Request) (int, interface{}, error) + +// ContextHandlerFunc is an equivalent to SimpleService's http.HandlerFunc. +type ContextHandlerFunc func(context.Context, http.ResponseWriter, *http.Request) + +// ServeHTTPContext is an implementation of ContextHandler interface. +func (h ContextHandlerFunc) ServeHTTPContext(ctx context.Context, rw http.ResponseWriter, req *http.Request) { + h(ctx, rw, req) +} + +// ContextHandler is an equivalent to http.Handler but with additional param. +type ContextHandler interface { + ServeHTTPContext(context.Context, http.ResponseWriter, *http.Request) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server.go new file mode 100644 index 000000000..5145ca458 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server.go @@ -0,0 +1,375 @@ +package server + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "runtime/debug" + "strings" + + "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/provider" + "github.com/prometheus/client_golang/prometheus" + netContext "golang.org/x/net/context" + "google.golang.org/appengine" + + metricscfg "github.com/NYTimes/gizmo/config/metrics" + "github.com/NYTimes/gizmo/web" +) + +// SimpleServer is a basic http Server implementation for +// serving SimpleService, JSONService or MixedService implementations. +type SimpleServer struct { + cfg *Config + + // exit chan for graceful shutdown + exit chan chan error + + // mux for routing + mux Router + + // tracks active requests + monitor *ActivityMonitor + + // for collecting metrics + mets provider.Provider + panicCounter metrics.Counter +} + +// NewSimpleServer will init the mux, exit channel and +// build the address from the given port. It will register the HealthCheckHandler +// at the given path and set up the shutDownHandler to be called on Stop(). +func NewSimpleServer(cfg *Config) *SimpleServer { + if cfg == nil { + cfg = &Config{} + } + mx := NewRouter(cfg) + if cfg.NotFoundHandler != nil { + mx.SetNotFoundHandler(cfg.NotFoundHandler) + } + + mets := newMetricsProvider(cfg) + return &SimpleServer{ + mux: mx, + cfg: cfg, + exit: make(chan chan error), + monitor: NewActivityMonitor(), + mets: mets, + panicCounter: mets.NewCounter("panic"), + } +} + +// ServeHTTP is SimpleServer's hook for metrics and safely executing each request. +func (s *SimpleServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + AddIPToContext(r) + + // only count non-LB requests + if r.URL.Path != s.cfg.HealthCheckPath { + s.monitor.CountRequest() + defer s.monitor.UncountRequest() + } + + s.safelyExecuteRequest(w, r) +} + +// UnexpectedServerError is returned with a 500 status code when SimpleServer recovers +// from a panic in a request. +var UnexpectedServerError = []byte("unexpected server error") + +// executeRequestSafely will prevent a panic in a request from bringing the server down. +func (s *SimpleServer) safelyExecuteRequest(w http.ResponseWriter, r *http.Request) { + + defer func() { + if x := recover(); x != nil { + // register a panic'd request with our metrics + s.panicCounter.Add(1) + + // log the panic for all the details later + LogWithFields(r).Errorf("simple server recovered from a panic\n%v: %v", x, string(debug.Stack())) + + // give the users our deepest regrets + w.WriteHeader(http.StatusInternalServerError) + if _, err := w.Write(UnexpectedServerError); err != nil { + LogWithFields(r).Warn("unable to write response: ", err) + } + } + }() + + // hand the request off to gorilla + s.mux.ServeHTTP(w, r) +} + +// Start will start the SimpleServer at it's configured address. +// If they are configured, this will start health checks and access logging. +func (s *SimpleServer) Start() error { + healthHandler := RegisterHealthHandler(s.cfg, s.monitor, s.mux) + s.cfg.HealthCheckPath = healthHandler.Path() + + // if expvar, register on our router + + switch s.cfg.Metrics.Type { + + case metricscfg.Expvar: + if s.cfg.Metrics.Path == "" { + s.cfg.Metrics.Path = "/debug/vars" + } + s.mux.HandleFunc("GET", s.cfg.Metrics.Path, expvarHandler) + + case metricscfg.Prometheus: + if s.cfg.Metrics.Path == "" { + s.cfg.Metrics.Path = "/metrics" + } + s.mux.HandleFunc("GET", s.cfg.Metrics.Path, + prometheus.InstrumentHandler("prometheus", prometheus.UninstrumentedHandler())) + } + + // if this is an App Engine setup, just run it here + if s.cfg.appEngine { + http.Handle("/", s) + appengine.Main() + return nil + } + + wrappedHandler, err := NewAccessLogMiddleware(s.cfg.HTTPAccessLog, s) + if err != nil { + Log.Fatalf("unable to create http access log: %s", err) + } + + srv := httpServer(wrappedHandler) + + l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.cfg.HTTPPort)) + if err != nil { + return err + } + + l = net.Listener(TCPKeepAliveListener{l.(*net.TCPListener)}) + + // add TLS if in the configs + if s.cfg.TLSCertFile != nil && s.cfg.TLSKeyFile != nil { + cert, err := tls.LoadX509KeyPair(*s.cfg.TLSCertFile, *s.cfg.TLSKeyFile) + if err != nil { + return err + } + srv.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + NextProtos: []string{"http/1.1"}, + } + + l = tls.NewListener(l, srv.TLSConfig) + } + + go func() { + if err := srv.Serve(l); err != nil { + Log.Error("encountered an error while serving listener: ", err) + } + }() + Log.Infof("Listening on %s", l.Addr().String()) + + // join the LB + go func() { + exit := <-s.exit + + // let the health check clean up if it needs to + if err := healthHandler.Stop(); err != nil { + Log.Warn("health check Stop returned with error: ", err) + } + + // flush any remaining metrics and close connections + s.mets.Stop() + + // stop the listener + exit <- l.Close() + }() + + return nil +} + +// Stop initiates the shutdown process and returns when +// the server completes. +func (s *SimpleServer) Stop() error { + ch := make(chan error) + s.exit <- ch + return <-ch +} + +// Register will accept and register SimpleServer, JSONService or MixedService implementations. +func (s *SimpleServer) Register(svcI Service) error { + prefix := svcI.Prefix() + // quick fix for backwards compatibility + prefix = strings.TrimRight(prefix, "/") + + var ( + js JSONService + ss SimpleService + cs ContextService + mcs MixedContextService + ) + + switch svc := svcI.(type) { + case MixedService: + js = svc + ss = svc + case SimpleService: + ss = svc + case JSONService: + js = svc + case MixedContextService: + mcs = svc + cs = svc + case ContextService: + cs = svc + default: + return errors.New("services for SimpleServers must implement the SimpleService, JSONService or MixedService interfaces") + } + + if ss != nil { + // register all simple endpoints with our wrapper + for path, epMethods := range ss.Endpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + s.mux.Handle(method, prefix+path, TimedAndCounted( + func(ep http.HandlerFunc, ss SimpleService) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // is it worth it to always close this? + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + Log.Warn("unable to close request body: ", err) + } + }() + } + + // call the func and return err or not + ss.Middleware(ep).ServeHTTP(w, r) + }) + }(ep, ss), + prefix+path, method, s.mets), + ) + } + } + } + + if js != nil { + // register all JSON endpoints with our wrapper + for path, epMethods := range js.JSONEndpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + s.mux.Handle(method, prefix+path, TimedAndCounted( + js.Middleware(JSONToHTTP(js.JSONMiddleware(ep))), + prefix+path, method, s.mets), + ) + } + } + } + + if cs != nil { + // register all context endpoints with our wrapper + for path, epMethods := range cs.ContextEndpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + s.mux.Handle(method, prefix+path, TimedAndCounted( + func(ep ContextHandlerFunc, cs ContextService) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // is it worth it to always close this? + if r.Body != nil { + defer func() { + if err := r.Body.Close(); err != nil { + Log.Warn("unable to close request body: ", err) + } + }() + } + // call the func and return err or not + cs.Middleware(ContextToHTTP(cs.ContextMiddleware(ep))).ServeHTTP(w, r) + }) + }(ep, cs), + prefix+path, method, s.mets), + ) + } + } + } + + if mcs != nil { + // register all context endpoints with our wrapper + for path, epMethods := range mcs.JSONEndpoints() { + for method, ep := range epMethods { + // set the function handle and register it to metrics + s.mux.Handle(method, prefix+path, TimedAndCounted( + mcs.Middleware(ContextToHTTP( + mcs.ContextMiddleware( + JSONContextToHTTP(mcs.JSONContextMiddleware(ep)), + ), + )), + prefix+path, method, s.mets), + ) + } + } + } + + RegisterProfiler(s.cfg, s.mux) + return nil +} + +// GetForwardedIP returns the "X-Forwarded-For" header value. +func GetForwardedIP(r *http.Request) string { + return r.Header.Get("X-Forwarded-For") +} + +// GetIP returns the IP address for the given request. +func GetIP(r *http.Request) (string, error) { + ip, ok := web.Vars(r)["ip"] + if ok { + return ip, nil + } + + // check real ip header first + ip = r.Header.Get("X-Real-IP") + if len(ip) > 0 { + return ip, nil + } + + // no nginx reverse proxy? + // get IP old fashioned way + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return "", fmt.Errorf("%q is not IP:port", r.RemoteAddr) + } + + userIP := net.ParseIP(ip) + if userIP == nil { + return "", fmt.Errorf("%q is not IP:port", r.RemoteAddr) + } + return userIP.String(), nil +} + +// ContextKey used to create context keys. +type ContextKey int + +const ( + // UserIPKey is key to set/retrieve value from context. + UserIPKey ContextKey = 0 + + // UserForwardForIPKey is key to set/retrieve value from context. + UserForwardForIPKey ContextKey = 1 +) + +// ContextWithUserIP returns new context with user ip address. +func ContextWithUserIP(ctx netContext.Context, r *http.Request) netContext.Context { + ip, err := GetIP(r) + if err != nil { + LogWithFields(r).Warningf("unable to get IP: %s", err) + return ctx + } + return netContext.WithValue(ctx, UserIPKey, ip) +} + +// ContextWithForwardForIP returns new context with forward for ip. +func ContextWithForwardForIP(ctx netContext.Context, r *http.Request) netContext.Context { + ip := GetForwardedIP(r) + if len(ip) > 0 { + ctx = netContext.WithValue(ctx, UserForwardForIPKey, ip) + } + + return ctx +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_go17_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_go17_test.go new file mode 100644 index 000000000..be7fa9fc9 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_go17_test.go @@ -0,0 +1,47 @@ +// +build go1.7 + +package server + +import ( + "context" + "fmt" + "net/http" + + "github.com/NYTimes/gizmo/web" +) + +type benchmarkContextService struct { + fast bool +} + +func (s *benchmarkContextService) Prefix() string { + return "/svc/v1" +} + +func (s *benchmarkContextService) ContextEndpoints() map[string]map[string]ContextHandlerFunc { + return map[string]map[string]ContextHandlerFunc{ + "/ctx/1/{something}/:something": map[string]ContextHandlerFunc{ + "GET": s.GetSimple, + }, + "/ctx/2": map[string]ContextHandlerFunc{ + "GET": s.GetSimpleNoParam, + }, + } +} + +func (s *benchmarkContextService) ContextMiddleware(h ContextHandler) ContextHandler { + return h +} + +func (s *benchmarkContextService) Middleware(h http.Handler) http.Handler { + return h +} + +func (s *benchmarkContextService) GetSimple(ctx context.Context, w http.ResponseWriter, r *http.Request) { + something := web.Vars(r)["something"] + fmt.Fprint(w, something) +} + +func (s *benchmarkContextService) GetSimpleNoParam(ctx context.Context, w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "ok") +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_pre_go17_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_pre_go17_test.go new file mode 100644 index 000000000..ee0400a5e --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_pre_go17_test.go @@ -0,0 +1,47 @@ +// +build !go1.7 + +package server + +import ( + "fmt" + "net/http" + + "github.com/NYTimes/gizmo/web" + "golang.org/x/net/context" +) + +type benchmarkContextService struct { + fast bool +} + +func (s *benchmarkContextService) Prefix() string { + return "/svc/v1" +} + +func (s *benchmarkContextService) ContextEndpoints() map[string]map[string]ContextHandlerFunc { + return map[string]map[string]ContextHandlerFunc{ + "/ctx/1/{something}/:something": map[string]ContextHandlerFunc{ + "GET": s.GetSimple, + }, + "/ctx/2": map[string]ContextHandlerFunc{ + "GET": s.GetSimpleNoParam, + }, + } +} + +func (s *benchmarkContextService) ContextMiddleware(h ContextHandler) ContextHandler { + return h +} + +func (s *benchmarkContextService) Middleware(h http.Handler) http.Handler { + return h +} + +func (s *benchmarkContextService) GetSimple(ctx context.Context, w http.ResponseWriter, r *http.Request) { + something := web.Vars(r)["something"] + fmt.Fprint(w, something) +} + +func (s *benchmarkContextService) GetSimpleNoParam(ctx context.Context, w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "ok") +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_test.go new file mode 100644 index 000000000..e8ef1f8ac --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/simple_server_test.go @@ -0,0 +1,385 @@ +package server + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/NYTimes/gizmo/web" +) + +func BenchmarkFastSimpleServer_NoParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkFastSimpleServer_WithParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/1/{something}/blah", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkSimpleServer_NoParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkSimpleServer_WithParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkSimpleService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/1/blah/:something", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +type benchmarkSimpleService struct { + fast bool +} + +func (s *benchmarkSimpleService) Prefix() string { + return "/svc/v1" +} + +func (s *benchmarkSimpleService) Endpoints() map[string]map[string]http.HandlerFunc { + return map[string]map[string]http.HandlerFunc{ + "/1/{something}/:something": map[string]http.HandlerFunc{ + "GET": s.GetSimple, + }, + "/2": map[string]http.HandlerFunc{ + "GET": s.GetSimpleNoParam, + }, + } +} + +func (s *benchmarkSimpleService) Middleware(h http.Handler) http.Handler { + return h +} + +func (s *benchmarkSimpleService) GetSimple(w http.ResponseWriter, r *http.Request) { + something := web.Vars(r)["something"] + fmt.Fprint(w, something) +} + +func (s *benchmarkSimpleService) GetSimpleNoParam(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "ok") +} + +func BenchmarkFastJSONServer_JSONPayload(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/1", bytes.NewBufferString(`{"hello":"hi","howdy":"yo"}`)) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} +func BenchmarkFastJSONServer_NoParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} +func BenchmarkFastJSONServer_WithParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/3/{something}/blah", bytes.NewBufferString(`{"hello":"hi","howdy":"yo"}`)) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkJSONServer_JSONPayload(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/1", bytes.NewBufferString(`{"hello":"hi","howdy":"yo"}`)) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkJSONServer_NoParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} +func BenchmarkJSONServer_WithParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkJSONService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("PUT", "/svc/v1/3/blah/:something", bytes.NewBufferString(`{"hello":"hi","howdy":"yo"}`)) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +type benchmarkJSONService struct { + fast bool +} + +func (s *benchmarkJSONService) Prefix() string { + return "/svc/v1" +} + +func (s *benchmarkJSONService) JSONEndpoints() map[string]map[string]JSONEndpoint { + return map[string]map[string]JSONEndpoint{ + "/1": map[string]JSONEndpoint{ + "PUT": s.PutJSON, + }, + "/2": map[string]JSONEndpoint{ + "GET": s.GetJSON, + }, + "/3/{something}/:something": map[string]JSONEndpoint{ + "GET": s.GetJSONParam, + }, + } +} + +func (s *benchmarkJSONService) JSONMiddleware(e JSONEndpoint) JSONEndpoint { + return e +} + +func (s *benchmarkJSONService) Middleware(h http.Handler) http.Handler { + return h +} + +func (s *benchmarkJSONService) PutJSON(r *http.Request) (int, interface{}, error) { + var hello testJSON + err := json.NewDecoder(r.Body).Decode(&hello) + if err != nil { + return http.StatusBadRequest, nil, err + } + return http.StatusOK, hello, nil +} + +func (s *benchmarkJSONService) GetJSON(r *http.Request) (int, interface{}, error) { + return http.StatusOK, &testJSON{"hi", "howdy"}, nil +} + +func (s *benchmarkJSONService) GetJSONParam(r *http.Request) (int, interface{}, error) { + something := web.Vars(r)["something"] + return http.StatusOK, &testJSON{"hi", something}, nil +} + +func BenchmarkFastContextSimpleServer_NoParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkContextService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/ctx/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkFastContextSimpleServer_WithParam(b *testing.B) { + cfg := &Config{RouterType: "fast", HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkContextService{true}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/ctx/1/{something}/blah", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkContextSimpleServer_NoParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkContextService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/ctx/2", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +func BenchmarkContextSimpleServer_WithParam(b *testing.B) { + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + srvr := NewSimpleServer(cfg) + RegisterHealthHandler(cfg, srvr.monitor, srvr.mux) + srvr.Register(&benchmarkContextService{}) + + w := httptest.NewRecorder() + r, _ := http.NewRequest("GET", "/svc/v1/ctx/1/blah/:something", nil) + r.RemoteAddr = "0.0.0.0:8080" + + for i := 0; i < b.N; i++ { + srvr.ServeHTTP(w, r) + } +} + +type testJSON struct { + Hello string `json:"hello"` + Howdy string `json:"howdy"` +} + +type testMixedService struct { + fast bool +} + +func (s *testMixedService) Prefix() string { + return "/svc/v1" +} + +func (s *testMixedService) JSONEndpoints() map[string]map[string]JSONEndpoint { + return map[string]map[string]JSONEndpoint{ + "/json": map[string]JSONEndpoint{ + "GET": s.GetJSON, + }, + } +} + +func (s *testMixedService) Endpoints() map[string]map[string]http.HandlerFunc { + return map[string]map[string]http.HandlerFunc{ + "/simple": map[string]http.HandlerFunc{ + "GET": s.GetSimple, + }, + } +} + +func (s *testMixedService) GetSimple(w http.ResponseWriter, r *http.Request) { + something := web.Vars(r)["something"] + fmt.Fprint(w, something) +} + +func (s *testMixedService) GetJSON(r *http.Request) (int, interface{}, error) { + return http.StatusOK, &testJSON{"hi", "howdy"}, nil +} + +func (s *testMixedService) JSONMiddleware(e JSONEndpoint) JSONEndpoint { + return e +} + +func (s *testMixedService) Middleware(h http.Handler) http.Handler { + return h +} + +type testInvalidService struct { + fast bool +} + +func (s *testInvalidService) Prefix() string { + return "/svc/v1" +} + +func (s *testInvalidService) Middleware(h http.Handler) http.Handler { + return h +} + +func TestFactory(*testing.T) { + // with config: + cfg := &Config{HealthCheckType: "simple", HealthCheckPath: "/status"} + NewSimpleServer(cfg) + + // without config: + NewSimpleServer(nil) +} + +func TestBasicRegistration(t *testing.T) { + s := NewSimpleServer(nil) + services := []Service{ + &benchmarkSimpleService{}, + &benchmarkJSONService{}, + &testMixedService{}, + &benchmarkContextService{}, + } + for _, svc := range services { + if err := s.Register(svc); err != nil { + t.Errorf("Basic registration of services should not encounter an error: %s\n", err) + } + } + + if err := s.Register(&testInvalidService{}); err == nil { + t.Error("Invalid services should produce an error in service registration") + } +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/tcp.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/tcp.go new file mode 100644 index 000000000..692264de7 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/server/tcp.go @@ -0,0 +1,38 @@ +package server + +import ( + "net" + "time" +) + +// TCPKeepAliveListener sets TCP keep-alive timeouts on accepted +// connections. It's used by ListenAndServe and ListenAndServeTLS so +// dead TCP connections (e.g. closing laptop mid-download) eventually +// go away. +// +// This is here because it is not exposed in the stdlib and +// we'd prefer to have a hold of the http.Server's net.Listener so we can close it +// on shutdown. +// +// Taken from here: https://golang.org/src/net/http/server.go?s=63121:63175#L2120 +type TCPKeepAliveListener struct { + *net.TCPListener +} + +// Accept accepts the next incoming call and returns the new +// connection. KeepAlivePeriod is set properly. +func (ln TCPKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return + } + err = tc.SetKeepAlive(true) + if err != nil { + return + } + err = tc.SetKeepAlivePeriod(3 * time.Minute) + if err != nil { + return + } + return tc, nil +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/doc.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/doc.go new file mode 100644 index 000000000..dff106d64 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/doc.go @@ -0,0 +1,4 @@ +/* +Package web contains a handful of very useful functions for parsing types from request queries and payloads. +*/ +package web diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func.go new file mode 100644 index 000000000..d2083ba1a --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func.go @@ -0,0 +1,103 @@ +package web + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "time" +) + +// Let's have generic errors for expected conditions. Typically +// these would be http.StatusBadRequest (400) +var ( + JSONContentType = "application/json; charset=UTF-8" + DateISOFormat = "2006-01-02" +) + +// ParseISODate is a handy function to accept +func ParseISODate(dateStr string) (date time.Time, err error) { + date, err = time.ParseInLocation(DateISOFormat, dateStr, time.Local) + return +} + +// ParseDateRange will look for and parse 'startDate' and 'endDate' ISO date +// strings in the given vars map. +func ParseDateRange(vars map[string]string) (startDate time.Time, endDate time.Time, err error) { + startDate, err = ParseISODate(vars["startDate"]) + if err != nil { + err = errors.New("please use a valid start date with a format of YYYY-MM-DD") + return + } + + endDate, err = ParseISODate(vars["endDate"]) + if err != nil { + err = errors.New("please use a valid end date with a format of YYYY-MM-DD") + return + } + + return +} + +// GetInt64Var is a helper to pull gorilla mux Vars. +// If the value is empty, it falls back to the URL +// query string. +// We are ignoring the error here bc we're assuming +// the path had a [0-9]+ descriptor on this var. +func GetInt64Var(r *http.Request, key string) int64 { + v := Vars(r)[key] + if len(v) == 0 { + va := r.URL.Query()[key] + if len(va) > 0 { + v = va[0] + } + } + i, _ := strconv.ParseInt(v, 10, 64) + + return i +} + +// GetUInt64Var is a helper to pull gorilla mux Vars. +// If the value is empty, it falls back to the URL +// query string. +// We are ignoring the error here bc we're assuming +// the path had a [0-9]+ descriptor on this var. +func GetUInt64Var(r *http.Request, key string) uint64 { + v := Vars(r)[key] + if len(v) == 0 { + va := r.URL.Query()[key] + if len(va) > 0 { + v = va[0] + } + } + i, _ := strconv.ParseUint(v, 10, 64) + return i +} + +// ParseDateRangeFullDay will look for and parse 'startDate' and 'endDate' ISO +// date strings in the given vars map. It will then set the startDate time to +// midnight and the endDate time to 23:59:59. +func ParseDateRangeFullDay(vars map[string]string) (startDate time.Time, endDate time.Time, err error) { + startDate, endDate, err = ParseDateRange(vars) + if err != nil { + return + } + + // set time to beginning of day + startDate = time.Date(startDate.Year(), startDate.Month(), startDate.Day(), 0, 0, + 0, 0, time.Local) + // set the time to the end of day + endDate = time.Date(endDate.Year(), endDate.Month(), endDate.Day(), 23, 59, + 59, 1000, time.Local) + return +} + +// ParseTruthyFalsy is a helper method to attempt to parse booleans in +// APIs that have no set contract on what a boolean should look like. +func ParseTruthyFalsy(flag interface{}) (result bool, err error) { + s := fmt.Sprint(flag) + if s == "" { + return false, nil + } + return strconv.ParseBool(s) +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func_test.go new file mode 100644 index 000000000..d943d99dc --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/func_test.go @@ -0,0 +1,403 @@ +package web_test + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/NYTimes/gizmo/web" + "github.com/gorilla/mux" +) + +func TestParseISODate(t *testing.T) { + tests := []struct { + given string + + want time.Time + wantErr bool + }{ + { + "2015-10-29", + + time.Date(2015, time.October, 29, 0, 0, 0, 0, time.Local), + false, + }, + { + "20151029", + + time.Time{}, + true, + }, + } + + for _, test := range tests { + got, gotErr := web.ParseISODate(test.given) + + if test.wantErr { + if gotErr == nil { + t.Error("expected an error and did not get one") + } + continue + } + + if gotErr != nil { + t.Error("did not expect an error but got one: ", gotErr) + } + + if !got.Equal(test.want) { + t.Errorf("got %#v, expected %#v: ", got, test.want) + } + + } +} + +func TestParseDateRangeFullDay(t *testing.T) { + tests := []struct { + given map[string]string + + wantStart time.Time + wantEnd time.Time + wantErr bool + }{ + { + map[string]string{ + "startDate": "2015-10-29", + "endDate": "2015-10-31", + }, + + time.Date(2015, time.October, 29, 0, 0, 0, 0, time.Local), + time.Date(2015, time.October, 31, 23, 59, 59, 1000, time.Local), + false, + }, + { + map[string]string{ + "startDate": "2015-10-29", + "endDate": "-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "startDate": "-10-29", + "endDate": "2015-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "endDate": "2015-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "startDate": "-10-29", + }, + + time.Time{}, + time.Time{}, + true, + }, + } + + for _, test := range tests { + gotStart, gotEnd, gotErr := web.ParseDateRangeFullDay(test.given) + + if test.wantErr { + if gotErr == nil { + t.Error("expected an error and did not get one") + } + continue + } + + if gotErr != nil { + t.Error("did not expect an error but got one: ", gotErr) + } + + if !gotStart.Equal(test.wantStart) { + t.Errorf("got start date of %#v, expected %#v: ", gotStart, test.wantStart) + } + + if !gotEnd.Equal(test.wantEnd) { + t.Errorf("got end date of %#v, expected %#v: ", gotStart, test.wantStart) + } + } +} + +func TestParseDateRange(t *testing.T) { + tests := []struct { + given map[string]string + + wantStart time.Time + wantEnd time.Time + wantErr bool + }{ + { + map[string]string{ + "startDate": "2015-10-29", + "endDate": "2015-10-31", + }, + + time.Date(2015, time.October, 29, 0, 0, 0, 0, time.Local), + time.Date(2015, time.October, 31, 0, 0, 0, 0, time.Local), + false, + }, + { + map[string]string{ + "startDate": "2015-10-29", + "endDate": "-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "startDate": "-10-29", + "endDate": "2015-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "endDate": "2015-10-31", + }, + + time.Time{}, + time.Time{}, + true, + }, + { + map[string]string{ + "startDate": "-10-29", + }, + + time.Time{}, + time.Time{}, + true, + }, + } + + for _, test := range tests { + gotStart, gotEnd, gotErr := web.ParseDateRange(test.given) + + if test.wantErr { + if gotErr == nil { + t.Error("expected an error and did not get one") + } + continue + } + + if gotErr != nil { + t.Error("did not expect an error but got one: ", gotErr) + } + + if !gotStart.Equal(test.wantStart) { + t.Errorf("got start date of %#v, expected %#v: ", gotStart, test.wantStart) + } + + if !gotEnd.Equal(test.wantEnd) { + t.Errorf("got end date of %#v, expected %#v: ", gotStart, test.wantStart) + } + } +} + +func TestGetUInt64Var(t *testing.T) { + + tests := []struct { + givenURL string + givenRoute string + + want uint64 + }{ + { + "/blah/123", + "/blah/{key}", + 123, + }, + { + "/blah/adsf", + "/blah/{key}", + 0, + }, + { + "/blah?key=123", + "/blah", + 123, + }, + { + "/blah?key=abc", + "/blah", + 0, + }, + } + + for _, test := range tests { + route := mux.NewRouter() + route.HandleFunc(test.givenRoute, func(w http.ResponseWriter, r *http.Request) { + web.SetRouteVars(r, mux.Vars(r)) + got := web.GetUInt64Var(r, "key") + if got != test.want { + t.Errorf("URL(%s): got int of %#v, expected %#v", test.givenURL, got, test.want) + } + }) + + r, _ := http.NewRequest("GET", test.givenURL, nil) + route.ServeHTTP(httptest.NewRecorder(), r) + } +} + +func TestGetInt64Var(t *testing.T) { + + tests := []struct { + givenURL string + givenRoute string + + want int64 + }{ + { + "/blah/123", + "/blah/{key}", + 123, + }, + { + "/blah/adsf", + "/blah/{key}", + 0, + }, + { + "/blah?key=123", + "/blah", + 123, + }, + { + "/blah?key=abc", + "/blah", + 0, + }, + } + + for _, test := range tests { + route := mux.NewRouter() + route.HandleFunc(test.givenRoute, func(w http.ResponseWriter, r *http.Request) { + web.SetRouteVars(r, mux.Vars(r)) + got := web.GetInt64Var(r, "key") + if got != test.want { + t.Errorf("URL(%s): got int of %#v, expected %#v", test.givenURL, got, test.want) + } + }) + + r, _ := http.NewRequest("GET", test.givenURL, nil) + route.ServeHTTP(httptest.NewRecorder(), r) + } +} + +func TestParseTruthyFalsy(t *testing.T) { + tests := []struct { + given interface{} + + want bool + wantErr bool + }{ + { + "true", + true, + false, + }, + { + "false", + false, + false, + }, + { + "0", + false, + false, + }, + { + "1", + true, + false, + }, + { + "", + false, + false, + }, + { + "nope!", + false, + true, + }, + { + 1, + true, + false, + }, + { + 0, + false, + false, + }, + { + 2, + false, + true, + }, + { + float64(0.0), + false, + false, + }, + { + float64(1.0), + true, + false, + }, + { + float64(2.0), + false, + true, + }, + { + true, + true, + false, + }, + { + false, + false, + false, + }, + } + + for _, test := range tests { + + got, gotErr := web.ParseTruthyFalsy(test.given) + + if test.wantErr != (gotErr != nil) { + t.Errorf("wantErr is %v, but got %s", test.wantErr, gotErr) + } + + if test.want != got { + t.Errorf("expected %v, got %v", test.want, got) + + } + + } + +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_gorilla.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_gorilla.go new file mode 100644 index 000000000..46a2a5cd0 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_gorilla.go @@ -0,0 +1,34 @@ +// +build !go1.7 + +package web + +import ( + "net/http" + + "github.com/gorilla/context" +) + +// Vars is a helper function for accessing route +// parameters from any server.Router implementation. This is the equivalent +// of using `mux.Vars(r)` with the Gorilla mux.Router. +func Vars(r *http.Request) map[string]string { + if rv := context.Get(r, varsKey); rv != nil { + vars, _ := rv.(map[string]string) + return vars + } + return nil +} + +// SetRouteVars will set the given value into into the request context +// with the shared 'vars' storage key. +func SetRouteVars(r *http.Request, val interface{}) { + if val != nil { + context.Set(r, varsKey, val) + } +} + +type contextKey int + +// key to set/retrieve URL params from a +// Gorilla request context. +const varsKey contextKey = 2 diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_native.go b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_native.go new file mode 100644 index 000000000..f93d031ee --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/gizmo/web/vars_native.go @@ -0,0 +1,40 @@ +// +build go1.7 + +package web + +import ( + "context" + "net/http" +) + +// Vars is a helper function for accessing route +// parameters from any server.Router implementation. This is the equivalent +// of using `mux.Vars(r)` with the Gorilla mux.Router. +func Vars(r *http.Request) map[string]string { + // vars doesnt exist yet, return empty map + rawVars := r.Context().Value(varsKey) + if rawVars == nil { + return map[string]string{} + } + + // for some reason, vars is wrong type, return empty map + vars, _ := rawVars.(map[string]string) + return vars +} + +// SetRouteVars will set the given value into into the request context +// with the shared 'vars' storage key. +func SetRouteVars(r *http.Request, val interface{}) { + if val == nil { + return + } + + r2 := r.WithContext(context.WithValue(r.Context(), varsKey, val)) + *r = *r2 +} + +type contextKey int + +// key to set/retrieve URL params from a +// Gorilla request context. +const varsKey contextKey = 2 diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/.travis.yml b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/.travis.yml new file mode 100644 index 000000000..0f99a3729 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/.travis.yml @@ -0,0 +1,5 @@ +language: go + +go: + - 1.4.2 + - 1.5.2 diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/README.md b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/README.md new file mode 100644 index 000000000..dfadcd77d --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/README.md @@ -0,0 +1,18 @@ +#logrotate file + + [![GoDoc](https://godoc.org/github.com/NYTimes/logrotate?status.svg)](https://godoc.org/github.com/nytimes/logrotate) [![Build Status](https://travis-ci.org/NYTimes/logrotate.svg?branch=master)](https://travis-ci.org/NYTimes/logrotate) + +`logrotated` can be configured to send a `SIGHUP` signal to a process after rotating it's logs. This library reopens the underlying `os.File` when a `SIGHUP` is received by the app. + +###Example +This is will enable all log calls to output to the log file without interruption when `logrotated` rotates the file. + + logfile, err := logrotate.NewFile("/log/path/here") + if err != nil { + log.Fatal(err) + } + + log.SetOutput(logfile) + + +ref: [http://linux.die.net/man/8/logrotate](http://linux.die.net/man/8/logrotate) diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate.go b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate.go new file mode 100644 index 000000000..1fbb8d18b --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate.go @@ -0,0 +1,74 @@ +package logrotate + +import ( + "fmt" + "os" + "os/signal" + "sync" + "syscall" + "time" +) + +// File wraps an *os.File and listens for a 'SIGHUP' signal from logrotated +// so it can reopen the new file. +type File struct { + *os.File + me sync.Mutex + path string + sighup chan os.Signal +} + +// NewFile creates a File pointer and kicks off the goroutine listening for +// SIGHUP signals. +func NewFile(path string) (*File, error) { + + lr := &File{ + me: sync.Mutex{}, + path: path, + sighup: make(chan os.Signal, 1), + } + + if err := lr.reopen(); err != nil { + return nil, err + } + + go func() { + signal.Notify(lr.sighup, syscall.SIGHUP) + + for _ = range lr.sighup { + fmt.Fprintf(os.Stderr, "%s: Reopening %q\n", time.Now(), lr.path) + if err := lr.reopen(); err != nil { + fmt.Fprintf(os.Stderr, "%s: Error reopening: %s\n", time.Now(), err) + } + } + }() + + return lr, nil + +} + +func (lr *File) reopen() (err error) { + lr.me.Lock() + defer lr.me.Unlock() + lr.File.Close() + lr.File, err = os.OpenFile(lr.path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + return +} + +// Write will write to the underlying file. It uses a sync.Mutex to ensure +// uninterrupted writes during logrotates. +func (lr *File) Write(b []byte) (int, error) { + lr.me.Lock() + defer lr.me.Unlock() + return lr.File.Write(b) +} + +// Close will stop the goroutine listening for SIGHUP signals and then close +// the underlying os.File. +func (lr *File) Close() error { + lr.me.Lock() + defer lr.me.Unlock() + signal.Stop(lr.sighup) + close(lr.sighup) + return lr.File.Close() +} diff --git a/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate_test.go b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate_test.go new file mode 100644 index 000000000..950a91e7b --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/NYTimes/logrotate/logrotate_test.go @@ -0,0 +1,47 @@ +package logrotate + +import ( + "fmt" + "os" + "syscall" + "testing" + "time" +) + +const TestPath = "/tmp/testfile" + +func TestRotate(t *testing.T) { + + file, err := NewFile(TestPath) + if err != nil { + t.Fatal(err) + } + defer os.Remove(TestPath) + defer os.Remove(TestPath + "2") + defer file.Close() + + go func() { + for { + time.Sleep(100 * time.Millisecond) + fmt.Fprintln(file, "tick") + } + }() + + time.Sleep(time.Second / 2) + + os.Rename(TestPath, TestPath+"2") + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Error(err) + return + } + + if err := proc.Signal(syscall.SIGHUP); err != nil { + t.Error(err) + return + } + + time.Sleep(time.Second / 2) + +} diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/.gitignore b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/.gitignore new file mode 100644 index 000000000..4c51178c9 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/.gitignore @@ -0,0 +1,2 @@ +\#* +.\#* \ No newline at end of file diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/LICENSE b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/LICENSE new file mode 100644 index 000000000..d23fea365 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 VividCortex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/README.md b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/README.md new file mode 100644 index 000000000..eeb14d366 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/README.md @@ -0,0 +1,80 @@ +# gohistogram - Histograms in Go + +![build status](https://circleci.com/gh/VividCortex/gohistogram.png?circle-token=d37ec652ea117165cd1b342400a801438f575209) + +This package provides [Streaming Approximate Histograms](https://vividcortex.com/blog/2013/07/08/streaming-approximate-histograms/) +for efficient quantile approximations. + +The histograms in this package are based on the algorithms found in +Ben-Haim & Yom-Tov's *A Streaming Parallel Decision Tree Algorithm* +([PDF](http://jmlr.org/papers/volume11/ben-haim10a/ben-haim10a.pdf)). +Histogram bins do not have a preset size. As values stream into +the histogram, bins are dynamically added and merged. + +Another implementation can be found in the Apache Hive project (see +[NumericHistogram](http://hive.apache.org/docs/r0.11.0/api/org/apache/hadoop/hive/ql/udf/generic/NumericHistogram.html)). + +An example: + +![histogram](http://i.imgur.com/5OplaRs.png) + +The accurate method of calculating quantiles (like percentiles) requires +data to be sorted. Streaming histograms make it possible to approximate +quantiles without sorting (or even individually storing) values. + +NumericHistogram is the more basic implementation of a streaming +histogram. WeightedHistogram implements bin values as exponentially-weighted +moving averages. + +A maximum bin size is passed as an argument to the constructor methods. A +larger bin size yields more accurate approximations at the cost of increased +memory utilization and performance. + +A picture of kittens: + +![stack of kittens](http://i.imgur.com/QxRTWAE.jpg) + +## Getting started + +### Using in your own code + + $ go get github.com/VividCortex/gohistogram + +```go +import "github.com/VividCortex/gohistogram" +``` + +### Running tests and making modifications + +Get the code into your workspace: + + $ cd $GOPATH + $ git clone git@github.com:VividCortex/gohistogram.git ./src/github.com/VividCortex/gohistogram + +You can run the tests now: + + $ cd src/github.com/VividCortex/gohistogram + $ go test . + +## API Documentation + +Full source documentation can be found [here][godoc]. + +[godoc]: http://godoc.org/github.com/VividCortex/gohistogram + +## Contributing + +We only accept pull requests for minor fixes or improvements. This includes: + +* Small bug fixes +* Typos +* Documentation or comments + +Please open issues to discuss new features. Pull requests for new features will be rejected, +so we recommend forking the repository and making changes in your fork for your use case. + +## License + +Copyright (c) 2013 VividCortex + +Released under MIT License. Check `LICENSE` file for details. diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram.go b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram.go new file mode 100644 index 000000000..ede21fd31 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram.go @@ -0,0 +1,23 @@ +package gohistogram + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +// Histogram is the interface that wraps the Add and Quantile methods. +type Histogram interface { + // Add adds a new value, n, to the histogram. Trimming is done + // automatically. + Add(n float64) + + // Quantile returns an approximation. + Quantile(n float64) (q float64) + + // String returns a string reprentation of the histogram, + // which is useful for printing to a terminal. + String() (str string) +} + +type bin struct { + value float64 + count float64 +} diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram_test.go b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram_test.go new file mode 100644 index 000000000..52f786f82 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/histogram_test.go @@ -0,0 +1,62 @@ +package gohistogram + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +import ( + "math" + "testing" +) + +func approx(x, y float64) bool { + return math.Abs(x-y) < 0.2 +} + +func TestHistogram(t *testing.T) { + h := NewHistogram(160) + for _, val := range testData { + h.Add(float64(val)) + } + if h.Count() != 14999 { + t.Errorf("Expected h.Count() to be 14999, got %v", h.Count()) + } + + if firstQ := h.Quantile(0.25); !approx(firstQ, 14) { + t.Errorf("Expected 25th percentile to be %v, got %v", 14, firstQ) + } + if median := h.Quantile(0.5); !approx(median, 18) { + t.Errorf("Expected 50th percentile to be %v, got %v", 18, median) + } + if thirdQ := h.Quantile(0.75); !approx(thirdQ, 22) { + t.Errorf("Expected 75th percentile to be %v, got %v", 22, thirdQ) + } + if cdf := h.CDF(18); !approx(cdf, 0.5) { + t.Errorf("Expected CDF(median) to be %v, got %v", 0.5, cdf) + } + if cdf := h.CDF(22); !approx(cdf, 0.75) { + t.Errorf("Expected CDF(3rd quartile) to be %v, got %v", 0.75, cdf) + } +} + +func TestWeightedHistogram(t *testing.T) { + h := NewWeightedHistogram(160, 1) + for _, val := range testData { + h.Add(float64(val)) + } + + if firstQ := h.Quantile(0.25); !approx(firstQ, 14) { + t.Errorf("Expected 25th percentile to be %v, got %v", 14, firstQ) + } + if median := h.Quantile(0.5); !approx(median, 18) { + t.Errorf("Expected 50th percentile to be %v, got %v", 18, median) + } + if thirdQ := h.Quantile(0.75); !approx(thirdQ, 22) { + t.Errorf("Expected 75th percentile to be %v, got %v", 22, thirdQ) + } + if cdf := h.CDF(18); !approx(cdf, 0.5) { + t.Errorf("Expected CDF(median) to be %v, got %v", 0.5, cdf) + } + if cdf := h.CDF(22); !approx(cdf, 0.75) { + t.Errorf("Expected CDF(3rd quartile) to be %v, got %v", 0.75, cdf) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/numerichistogram.go b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/numerichistogram.go new file mode 100644 index 000000000..20dea740d --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/numerichistogram.go @@ -0,0 +1,160 @@ +package gohistogram + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +import ( + "fmt" +) + +type NumericHistogram struct { + bins []bin + maxbins int + total uint64 +} + +// NewHistogram returns a new NumericHistogram with a maximum of n bins. +// +// There is no "optimal" bin count, but somewhere between 20 and 80 bins +// should be sufficient. +func NewHistogram(n int) *NumericHistogram { + return &NumericHistogram{ + bins: make([]bin, 0), + maxbins: n, + total: 0, + } +} + +func (h *NumericHistogram) Add(n float64) { + defer h.trim() + h.total++ + for i := range h.bins { + if h.bins[i].value == n { + h.bins[i].count++ + return + } + + if h.bins[i].value > n { + + newbin := bin{value: n, count: 1} + head := append(make([]bin, 0), h.bins[0:i]...) + + head = append(head, newbin) + tail := h.bins[i:] + h.bins = append(head, tail...) + return + } + } + + h.bins = append(h.bins, bin{count: 1, value: n}) +} + +func (h *NumericHistogram) Quantile(q float64) float64 { + count := q * float64(h.total) + for i := range h.bins { + count -= float64(h.bins[i].count) + + if count <= 0 { + return h.bins[i].value + } + } + + return -1 +} + +// CDF returns the value of the cumulative distribution function +// at x +func (h *NumericHistogram) CDF(x float64) float64 { + count := 0.0 + for i := range h.bins { + if h.bins[i].value <= x { + count += float64(h.bins[i].count) + } + } + + return count / float64(h.total) +} + +// Mean returns the sample mean of the distribution +func (h *NumericHistogram) Mean() float64 { + if h.total == 0 { + return 0 + } + + sum := 0.0 + + for i := range h.bins { + sum += h.bins[i].value * h.bins[i].count + } + + return sum / float64(h.total) +} + +// Variance returns the variance of the distribution +func (h *NumericHistogram) Variance() float64 { + if h.total == 0 { + return 0 + } + + sum := 0.0 + mean := h.Mean() + + for i := range h.bins { + sum += (h.bins[i].count * (h.bins[i].value - mean) * (h.bins[i].value - mean)) + } + + return sum / float64(h.total) +} + +func (h *NumericHistogram) Count() float64 { + return float64(h.total) +} + +// trim merges adjacent bins to decrease the bin count to the maximum value +func (h *NumericHistogram) trim() { + for len(h.bins) > h.maxbins { + // Find closest bins in terms of value + minDelta := 1e99 + minDeltaIndex := 0 + for i := range h.bins { + if i == 0 { + continue + } + + if delta := h.bins[i].value - h.bins[i-1].value; delta < minDelta { + minDelta = delta + minDeltaIndex = i + } + } + + // We need to merge bins minDeltaIndex-1 and minDeltaIndex + totalCount := h.bins[minDeltaIndex-1].count + h.bins[minDeltaIndex].count + mergedbin := bin{ + value: (h.bins[minDeltaIndex-1].value* + h.bins[minDeltaIndex-1].count + + h.bins[minDeltaIndex].value* + h.bins[minDeltaIndex].count) / + totalCount, // weighted average + count: totalCount, // summed heights + } + head := append(make([]bin, 0), h.bins[0:minDeltaIndex-1]...) + tail := append([]bin{mergedbin}, h.bins[minDeltaIndex+1:]...) + h.bins = append(head, tail...) + } +} + +// String returns a string reprentation of the histogram, +// which is useful for printing to a terminal. +func (h *NumericHistogram) String() (str string) { + str += fmt.Sprintln("Total:", h.total) + + for i := range h.bins { + var bar string + for j := 0; j < int(float64(h.bins[i].count)/float64(h.total)*200); j++ { + bar += "." + } + str += fmt.Sprintln(h.bins[i].value, "\t", bar) + } + + return +} diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/sample_data_test.go b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/sample_data_test.go new file mode 100644 index 000000000..3952041cf --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/sample_data_test.go @@ -0,0 +1,15006 @@ +package gohistogram + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +var testData = []int{ + 8, + 13, + 17, + 16, + 15, + 14, + 14, + 20, + 16, + 54, + 34, + 19, + 15, + 19, + 17, + 14, + 15, + 10, + 10, + 9, + 10, + 12, + 20, + 17, + 12, + 10, + 15, + 15, + 11, + 13, + 16, + 11, + 17, + 17, + 18, + 20, + 25, + 16, + 19, + 31, + 83, + 57, + 10, + 12, + 13, + 19, + 10, + 39, + 20, + 10, + 11, + 3, + 13, + 14, + 15, + 11, + 12, + 17, + 16, + 13, + 15, + 15, + 12, + 16, + 13, + 25, + 14, + 11, + 26, + 24, + 37, + 14, + 15, + 10, + 22, + 16, + 19, + 33, + 11, + 10, + 31, + 18, + 13, + 18, + 11, + 15, + 11, + 19, + 13, + 14, + 12, + 9, + 16, + 24, + 14, + 19, + 16, + 12, + 37, + 17, + 25, + 15, + 15, + 13, + 8, + 18, + 26, + 34, + 9, + 11, + 12, + 29, + 16, + 16, + 8, + 10, + 9, + 22, + 17, + 18, + 19, + 11, + 15, + 13, + 16, + 28, + 17, + 19, + 111, + 117, + 32, + 27, + 14, + 13, + 9, + 14, + 22, + 31, + 18, + 17, + 15, + 16, + 17, + 11, + 11, + 4, + 13, + 17, + 18, + 17, + 11, + 12, + 16, + 16, + 14, + 19, + 18, + 54, + 18, + 15, + 27, + 12, + 10, + 11, + 17, + 24, + 16, + 17, + 12, + 20, + 22, + 9, + 17, + 19, + 30, + 16, + 10, + 12, + 13, + 21, + 14, + 15, + 20, + 21, + 16, + 20, + 9, + 55, + 40, + 12, + 17, + 18, + 20, + 17, + 14, + 35, + 16, + 21, + 26, + 17, + 16, + 9, + 11, + 9, + 9, + 17, + 11, + 17, + 10, + 12, + 12, + 12, + 20, + 14, + 26, + 19, + 36, + 21, + 22, + 12, + 12, + 13, + 14, + 14, + 14, + 20, + 16, + 11, + 18, + 13, + 13, + 16, + 14, + 11, + 12, + 13, + 13, + 13, + 15, + 14, + 16, + 17, + 14, + 11, + 17, + 14, + 29, + 16, + 38, + 17, + 11, + 13, + 17, + 17, + 28, + 15, + 14, + 13, + 19, + 15, + 11, + 15, + 11, + 10, + 16, + 15, + 13, + 14, + 15, + 11, + 16, + 16, + 17, + 9, + 8, + 31, + 100, + 146, + 76, + 12, + 12, + 12, + 14, + 9, + 34, + 11, + 17, + 24, + 16, + 17, + 15, + 15, + 21, + 14, + 18, + 14, + 9, + 22, + 14, + 19, + 18, + 12, + 16, + 7, + 21, + 31, + 19, + 11, + 18, + 15, + 13, + 14, + 12, + 27, + 17, + 15, + 19, + 20, + 23, + 21, + 8, + 23, + 12, + 16, + 14, + 15, + 21, + 12, + 9, + 17, + 20, + 17, + 13, + 25, + 12, + 82, + 48, + 18, + 20, + 24, + 14, + 11, + 15, + 26, + 13, + 17, + 23, + 14, + 32, + 20, + 18, + 11, + 16, + 12, + 15, + 17, + 11, + 14, + 19, + 16, + 14, + 15, + 13, + 17, + 37, + 99, + 30, + 18, + 18, + 12, + 9, + 15, + 15, + 29, + 10, + 16, + 10, + 17, + 15, + 15, + 9, + 9, + 14, + 8, + 17, + 10, + 21, + 22, + 12, + 15, + 18, + 12, + 11, + 15, + 48, + 17, + 29, + 11, + 18, + 21, + 11, + 8, + 13, + 18, + 16, + 11, + 13, + 21, + 19, + 12, + 11, + 15, + 18, + 15, + 17, + 16, + 13, + 17, + 17, + 8, + 8, + 9, + 8, + 17, + 27, + 14, + 14, + 11, + 14, + 19, + 17, + 24, + 13, + 15, + 9, + 12, + 17, + 14, + 18, + 12, + 7, + 10, + 21, + 18, + 13, + 13, + 11, + 27, + 17, + 16, + 23, + 15, + 15, + 72, + 117, + 76, + 26, + 33, + 11, + 13, + 11, + 29, + 75, + 16, + 15, + 15, + 18, + 13, + 11, + 11, + 17, + 4, + 13, + 17, + 12, + 9, + 16, + 28, + 12, + 14, + 11, + 12, + 19, + 70, + 17, + 13, + 13, + 19, + 14, + 11, + 21, + 27, + 9, + 21, + 17, + 17, + 12, + 12, + 14, + 10, + 16, + 16, + 13, + 13, + 17, + 14, + 8, + 12, + 14, + 11, + 20, + 14, + 29, + 32, + 48, + 16, + 12, + 16, + 23, + 11, + 17, + 31, + 17, + 16, + 12, + 14, + 22, + 10, + 12, + 7, + 20, + 9, + 18, + 14, + 17, + 10, + 22, + 17, + 20, + 16, + 14, + 15, + 28, + 18, + 18, + 18, + 15, + 13, + 16, + 17, + 38, + 14, + 6, + 13, + 11, + 23, + 17, + 16, + 9, + 13, + 17, + 13, + 15, + 15, + 20, + 28, + 18, + 22, + 18, + 7, + 15, + 57, + 77, + 30, + 15, + 15, + 16, + 13, + 19, + 14, + 24, + 11, + 21, + 17, + 17, + 11, + 12, + 20, + 17, + 10, + 10, + 13, + 17, + 14, + 10, + 13, + 11, + 20, + 9, + 11, + 12, + 44, + 66, + 30, + 13, + 18, + 15, + 13, + 10, + 28, + 14, + 13, + 11, + 15, + 12, + 13, + 14, + 15, + 12, + 10, + 12, + 11, + 13, + 22, + 17, + 17, + 15, + 14, + 13, + 22, + 17, + 50, + 18, + 13, + 13, + 14, + 14, + 10, + 16, + 18, + 11, + 21, + 13, + 23, + 14, + 25, + 11, + 13, + 8, + 7, + 16, + 14, + 19, + 11, + 10, + 18, + 18, + 8, + 20, + 14, + 18, + 20, + 13, + 20, + 20, + 11, + 12, + 14, + 31, + 13, + 11, + 14, + 8, + 14, + 9, + 13, + 14, + 22, + 20, + 9, + 21, + 7, + 17, + 12, + 13, + 20, + 18, + 15, + 12, + 23, + 33, + 46, + 23, + 15, + 14, + 14, + 17, + 13, + 19, + 20, + 13, + 19, + 15, + 10, + 3, + 14, + 15, + 18, + 15, + 16, + 11, + 22, + 13, + 18, + 18, + 14, + 20, + 13, + 12, + 26, + 11, + 11, + 12, + 12, + 18, + 11, + 9, + 26, + 17, + 18, + 15, + 12, + 14, + 13, + 13, + 11, + 14, + 17, + 15, + 7, + 17, + 19, + 10, + 13, + 11, + 15, + 18, + 22, + 19, + 18, + 20, + 41, + 16, + 16, + 18, + 20, + 18, + 37, + 17, + 15, + 30, + 16, + 16, + 32, + 21, + 12, + 20, + 17, + 19, + 13, + 10, + 17, + 9, + 18, + 21, + 16, + 19, + 12, + 45, + 44, + 24, + 18, + 17, + 14, + 13, + 17, + 18, + 24, + 7, + 26, + 20, + 17, + 14, + 21, + 15, + 16, + 21, + 14, + 14, + 11, + 9, + 15, + 18, + 11, + 17, + 16, + 16, + 19, + 30, + 15, + 18, + 11, + 15, + 13, + 13, + 13, + 20, + 18, + 8, + 14, + 10, + 15, + 8, + 17, + 17, + 17, + 8, + 11, + 18, + 16, + 10, + 16, + 15, + 16, + 12, + 15, + 10, + 31, + 25, + 25, + 13, + 15, + 12, + 16, + 20, + 20, + 19, + 22, + 15, + 14, + 20, + 12, + 14, + 14, + 11, + 11, + 10, + 18, + 14, + 15, + 19, + 10, + 13, + 24, + 12, + 22, + 18, + 80, + 67, + 85, + 14, + 15, + 12, + 18, + 22, + 9, + 17, + 9, + 10, + 9, + 14, + 15, + 13, + 11, + 15, + 21, + 12, + 20, + 18, + 22, + 20, + 14, + 18, + 16, + 15, + 14, + 17, + 21, + 21, + 17, + 13, + 9, + 19, + 12, + 10, + 34, + 13, + 15, + 20, + 11, + 23, + 17, + 11, + 15, + 12, + 15, + 15, + 11, + 19, + 20, + 14, + 14, + 21, + 13, + 17, + 19, + 20, + 60, + 22, + 15, + 12, + 12, + 12, + 10, + 12, + 20, + 10, + 13, + 11, + 21, + 15, + 20, + 16, + 9, + 11, + 17, + 22, + 7, + 15, + 18, + 12, + 17, + 19, + 14, + 17, + 15, + 23, + 23, + 24, + 15, + 18, + 16, + 15, + 21, + 31, + 17, + 21, + 13, + 13, + 10, + 20, + 20, + 21, + 15, + 19, + 16, + 13, + 12, + 16, + 14, + 10, + 15, + 17, + 24, + 16, + 63, + 143, + 109, + 66, + 16, + 15, + 17, + 19, + 15, + 14, + 16, + 12, + 18, + 13, + 18, + 21, + 13, + 12, + 16, + 13, + 19, + 13, + 8, + 17, + 11, + 6, + 13, + 11, + 13, + 20, + 36, + 23, + 20, + 20, + 18, + 19, + 16, + 16, + 19, + 10, + 12, + 17, + 12, + 26, + 14, + 10, + 17, + 12, + 8, + 13, + 10, + 13, + 15, + 15, + 22, + 15, + 22, + 18, + 16, + 36, + 49, + 16, + 20, + 12, + 10, + 16, + 14, + 21, + 15, + 10, + 14, + 13, + 25, + 18, + 17, + 14, + 18, + 23, + 18, + 24, + 14, + 15, + 18, + 28, + 16, + 18, + 23, + 18, + 21, + 76, + 34, + 17, + 14, + 22, + 22, + 21, + 15, + 34, + 16, + 31, + 24, + 22, + 7, + 18, + 13, + 18, + 21, + 14, + 18, + 17, + 18, + 13, + 15, + 16, + 21, + 16, + 23, + 20, + 81, + 124, + 32, + 21, + 17, + 14, + 16, + 20, + 21, + 22, + 19, + 18, + 17, + 17, + 22, + 18, + 11, + 20, + 12, + 22, + 20, + 30, + 16, + 13, + 19, + 18, + 14, + 13, + 56, + 21, + 17, + 18, + 13, + 21, + 26, + 15, + 20, + 15, + 17, + 15, + 13, + 12, + 17, + 15, + 16, + 19, + 12, + 15, + 15, + 12, + 20, + 17, + 19, + 23, + 17, + 20, + 25, + 24, + 24, + 26, + 29, + 25, + 21, + 13, + 24, + 19, + 78, + 22, + 22, + 14, + 24, + 22, + 18, + 15, + 16, + 18, + 7, + 15, + 16, + 13, + 11, + 35, + 21, + 19, + 16, + 19, + 16, + 64, + 38, + 16, + 16, + 21, + 18, + 16, + 13, + 9, + 11, + 16, + 15, + 14, + 13, + 25, + 18, + 18, + 9, + 31, + 13, + 16, + 13, + 19, + 18, + 25, + 9, + 21, + 15, + 20, + 16, + 41, + 62, + 58, + 11, + 23, + 13, + 17, + 16, + 34, + 14, + 15, + 20, + 14, + 20, + 12, + 17, + 13, + 24, + 17, + 18, + 22, + 17, + 19, + 9, + 15, + 19, + 17, + 10, + 17, + 34, + 43, + 32, + 14, + 14, + 12, + 12, + 11, + 16, + 23, + 14, + 13, + 20, + 10, + 15, + 20, + 19, + 19, + 16, + 17, + 22, + 16, + 25, + 19, + 17, + 18, + 16, + 18, + 17, + 13, + 132, + 202, + 174, + 25, + 13, + 20, + 24, + 12, + 33, + 20, + 13, + 16, + 19, + 17, + 13, + 26, + 5, + 11, + 13, + 16, + 22, + 13, + 19, + 15, + 22, + 17, + 22, + 18, + 13, + 54, + 87, + 51, + 13, + 13, + 14, + 17, + 10, + 14, + 32, + 22, + 16, + 13, + 19, + 16, + 19, + 18, + 21, + 16, + 22, + 21, + 18, + 19, + 15, + 24, + 13, + 18, + 16, + 28, + 19, + 38, + 16, + 18, + 17, + 17, + 19, + 11, + 16, + 22, + 18, + 18, + 16, + 15, + 15, + 19, + 18, + 19, + 14, + 14, + 13, + 17, + 14, + 19, + 24, + 16, + 20, + 22, + 9, + 18, + 25, + 26, + 26, + 14, + 13, + 16, + 13, + 13, + 22, + 50, + 17, + 24, + 16, + 24, + 19, + 15, + 19, + 18, + 18, + 18, + 18, + 19, + 8, + 12, + 9, + 21, + 18, + 21, + 19, + 4, + 39, + 32, + 22, + 10, + 22, + 12, + 20, + 15, + 13, + 19, + 15, + 18, + 25, + 16, + 22, + 16, + 18, + 10, + 16, + 19, + 16, + 14, + 10, + 15, + 16, + 16, + 15, + 14, + 9, + 18, + 43, + 12, + 21, + 11, + 18, + 13, + 15, + 16, + 16, + 15, + 20, + 23, + 17, + 18, + 19, + 15, + 13, + 15, + 15, + 17, + 15, + 15, + 19, + 15, + 13, + 23, + 22, + 17, + 9, + 31, + 46, + 30, + 30, + 16, + 18, + 15, + 10, + 21, + 17, + 15, + 15, + 10, + 14, + 13, + 12, + 23, + 18, + 19, + 21, + 17, + 15, + 13, + 13, + 15, + 22, + 17, + 13, + 16, + 20, + 47, + 16, + 23, + 16, + 14, + 11, + 14, + 18, + 23, + 12, + 25, + 17, + 23, + 16, + 16, + 19, + 13, + 14, + 10, + 11, + 16, + 28, + 23, + 19, + 19, + 11, + 15, + 24, + 23, + 31, + 42, + 18, + 18, + 11, + 18, + 14, + 14, + 22, + 21, + 10, + 13, + 17, + 15, + 23, + 16, + 21, + 15, + 11, + 23, + 15, + 18, + 19, + 11, + 17, + 24, + 16, + 22, + 18, + 11, + 84, + 159, + 80, + 12, + 10, + 20, + 23, + 17, + 24, + 55, + 12, + 20, + 7, + 16, + 21, + 12, + 18, + 22, + 18, + 18, + 12, + 16, + 20, + 13, + 19, + 26, + 18, + 22, + 25, + 23, + 33, + 28, + 14, + 21, + 10, + 24, + 25, + 15, + 42, + 18, + 17, + 24, + 37, + 16, + 10, + 11, + 13, + 14, + 24, + 19, + 21, + 22, + 17, + 32, + 15, + 17, + 26, + 18, + 20, + 43, + 49, + 22, + 18, + 20, + 21, + 21, + 18, + 27, + 18, + 17, + 18, + 12, + 20, + 17, + 20, + 17, + 12, + 15, + 24, + 26, + 16, + 15, + 15, + 27, + 17, + 12, + 24, + 20, + 16, + 64, + 32, + 18, + 21, + 37, + 24, + 22, + 19, + 29, + 22, + 18, + 19, + 15, + 14, + 14, + 12, + 14, + 20, + 16, + 16, + 21, + 10, + 16, + 15, + 19, + 22, + 20, + 10, + 13, + 47, + 16, + 27, + 15, + 21, + 14, + 16, + 20, + 26, + 15, + 17, + 19, + 18, + 21, + 23, + 13, + 15, + 10, + 15, + 11, + 18, + 19, + 22, + 15, + 18, + 12, + 24, + 30, + 21, + 18, + 18, + 20, + 16, + 15, + 11, + 10, + 14, + 22, + 15, + 20, + 12, + 17, + 14, + 11, + 20, + 27, + 19, + 14, + 17, + 13, + 15, + 14, + 10, + 12, + 17, + 18, + 11, + 21, + 16, + 35, + 30, + 42, + 31, + 34, + 19, + 24, + 24, + 21, + 21, + 23, + 19, + 17, + 24, + 25, + 16, + 19, + 20, + 19, + 15, + 18, + 21, + 21, + 18, + 27, + 26, + 34, + 26, + 23, + 26, + 130, + 200, + 165, + 90, + 23, + 19, + 31, + 13, + 28, + 16, + 22, + 27, + 16, + 21, + 22, + 24, + 17, + 11, + 13, + 23, + 23, + 26, + 22, + 22, + 17, + 21, + 19, + 26, + 20, + 41, + 107, + 112, + 101, + 34, + 38, + 33, + 34, + 37, + 76, + 31, + 29, + 20, + 44, + 23, + 29, + 24, + 25, + 25, + 27, + 23, + 26, + 35, + 28, + 41, + 24, + 24, + 30, + 36, + 29, + 65, + 43, + 51, + 29, + 26, + 20, + 28, + 21, + 29, + 22, + 20, + 18, + 16, + 15, + 14, + 18, + 21, + 21, + 15, + 21, + 20, + 18, + 18, + 17, + 19, + 20, + 19, + 19, + 13, + 34, + 50, + 29, + 34, + 34, + 31, + 33, + 40, + 39, + 43, + 37, + 31, + 40, + 39, + 41, + 39, + 41, + 48, + 39, + 42, + 44, + 36, + 43, + 39, + 44, + 41, + 37, + 36, + 37, + 39, + 57, + 42, + 45, + 31, + 25, + 28, + 27, + 19, + 27, + 24, + 31, + 23, + 24, + 22, + 24, + 23, + 16, + 14, + 15, + 15, + 15, + 17, + 13, + 17, + 16, + 15, + 19, + 16, + 13, + 18, + 46, + 21, + 24, + 24, + 28, + 29, + 32, + 40, + 56, + 28, + 33, + 35, + 37, + 29, + 31, + 34, + 37, + 34, + 36, + 34, + 36, + 30, + 42, + 36, + 43, + 41, + 47, + 50, + 43, + 70, + 153, + 78, + 42, + 35, + 34, + 41, + 26, + 31, + 54, + 16, + 25, + 27, + 19, + 25, + 15, + 25, + 17, + 15, + 19, + 19, + 24, + 30, + 28, + 12, + 23, + 22, + 34, + 22, + 18, + 64, + 36, + 64, + 55, + 37, + 48, + 47, + 56, + 77, + 49, + 55, + 47, + 54, + 55, + 51, + 57, + 45, + 55, + 49, + 46, + 46, + 39, + 45, + 45, + 51, + 50, + 53, + 45, + 48, + 65, + 78, + 56, + 48, + 43, + 37, + 27, + 28, + 23, + 34, + 26, + 25, + 22, + 26, + 27, + 24, + 23, + 15, + 21, + 22, + 23, + 18, + 17, + 18, + 14, + 19, + 15, + 18, + 13, + 19, + 76, + 143, + 136, + 34, + 28, + 39, + 41, + 53, + 55, + 53, + 56, + 52, + 62, + 56, + 55, + 52, + 57, + 70, + 53, + 58, + 59, + 55, + 65, + 67, + 57, + 57, + 56, + 59, + 55, + 54, + 68, + 60, + 62, + 45, + 49, + 51, + 29, + 32, + 46, + 22, + 20, + 27, + 24, + 29, + 25, + 20, + 27, + 23, + 16, + 22, + 19, + 24, + 19, + 24, + 22, + 18, + 29, + 32, + 24, + 44, + 37, + 51, + 29, + 32, + 40, + 48, + 50, + 62, + 106, + 46, + 65, + 50, + 41, + 62, + 67, + 48, + 53, + 65, + 49, + 61, + 64, + 65, + 46, + 55, + 55, + 68, + 53, + 51, + 59, + 132, + 49, + 59, + 74, + 69, + 51, + 52, + 69, + 80, + 76, + 60, + 61, + 50, + 41, + 46, + 44, + 29, + 30, + 32, + 38, + 18, + 33, + 28, + 21, + 30, + 25, + 29, + 27, + 25, + 41, + 104, + 47, + 26, + 20, + 17, + 25, + 27, + 21, + 32, + 23, + 24, + 29, + 23, + 23, + 30, + 26, + 26, + 23, + 28, + 28, + 29, + 19, + 22, + 26, + 25, + 18, + 23, + 23, + 22, + 29, + 78, + 35, + 30, + 23, + 26, + 25, + 23, + 33, + 20, + 21, + 22, + 27, + 27, + 24, + 24, + 23, + 21, + 33, + 22, + 21, + 25, + 17, + 31, + 32, + 21, + 25, + 15, + 23, + 24, + 102, + 85, + 27, + 24, + 21, + 29, + 25, + 19, + 26, + 22, + 16, + 19, + 29, + 16, + 23, + 20, + 21, + 31, + 22, + 25, + 25, + 22, + 17, + 28, + 28, + 19, + 22, + 24, + 20, + 42, + 53, + 38, + 19, + 20, + 19, + 22, + 20, + 15, + 25, + 28, + 33, + 21, + 26, + 23, + 37, + 20, + 25, + 31, + 22, + 22, + 31, + 17, + 27, + 16, + 20, + 22, + 18, + 29, + 26, + 115, + 95, + 27, + 25, + 25, + 16, + 18, + 18, + 31, + 12, + 5, + 23, + 17, + 10, + 15, + 11, + 16, + 23, + 19, + 30, + 21, + 19, + 22, + 22, + 20, + 19, + 30, + 18, + 28, + 24, + 98, + 119, + 49, + 17, + 12, + 17, + 34, + 21, + 36, + 21, + 20, + 18, + 18, + 15, + 14, + 19, + 19, + 16, + 12, + 25, + 14, + 13, + 19, + 14, + 19, + 13, + 15, + 20, + 20, + 42, + 47, + 33, + 17, + 20, + 19, + 18, + 22, + 17, + 20, + 15, + 22, + 20, + 17, + 26, + 16, + 21, + 24, + 19, + 17, + 22, + 15, + 26, + 16, + 26, + 20, + 16, + 21, + 18, + 21, + 32, + 25, + 23, + 21, + 21, + 21, + 22, + 19, + 31, + 30, + 20, + 18, + 15, + 16, + 38, + 16, + 24, + 21, + 19, + 24, + 24, + 20, + 34, + 26, + 22, + 16, + 27, + 55, + 143, + 115, + 19, + 24, + 19, + 15, + 25, + 18, + 42, + 14, + 18, + 16, + 27, + 14, + 18, + 27, + 23, + 18, + 15, + 22, + 15, + 21, + 13, + 10, + 15, + 15, + 18, + 17, + 11, + 76, + 162, + 142, + 28, + 17, + 23, + 14, + 19, + 25, + 21, + 21, + 20, + 15, + 23, + 26, + 15, + 29, + 13, + 16, + 21, + 16, + 18, + 31, + 9, + 15, + 15, + 13, + 18, + 21, + 27, + 34, + 22, + 21, + 14, + 18, + 23, + 18, + 14, + 21, + 20, + 16, + 19, + 22, + 25, + 16, + 19, + 19, + 20, + 23, + 21, + 17, + 9, + 20, + 16, + 17, + 20, + 24, + 18, + 26, + 76, + 34, + 40, + 15, + 21, + 23, + 13, + 16, + 15, + 20, + 22, + 22, + 15, + 24, + 17, + 15, + 14, + 21, + 8, + 14, + 26, + 14, + 14, + 13, + 13, + 12, + 16, + 19, + 22, + 25, + 40, + 21, + 17, + 16, + 12, + 15, + 17, + 19, + 26, + 17, + 17, + 26, + 19, + 21, + 16, + 22, + 16, + 13, + 21, + 11, + 24, + 17, + 24, + 16, + 21, + 21, + 22, + 16, + 16, + 60, + 28, + 30, + 17, + 13, + 17, + 18, + 18, + 16, + 21, + 21, + 25, + 14, + 26, + 26, + 22, + 22, + 15, + 22, + 24, + 24, + 36, + 12, + 21, + 21, + 29, + 13, + 13, + 22, + 20, + 57, + 15, + 18, + 16, + 24, + 17, + 19, + 12, + 44, + 19, + 23, + 16, + 16, + 21, + 17, + 23, + 13, + 34, + 17, + 27, + 20, + 16, + 20, + 23, + 13, + 22, + 19, + 21, + 17, + 30, + 51, + 46, + 18, + 14, + 18, + 14, + 18, + 20, + 19, + 22, + 19, + 26, + 15, + 17, + 36, + 20, + 22, + 18, + 21, + 22, + 16, + 21, + 19, + 18, + 21, + 16, + 20, + 15, + 23, + 74, + 176, + 216, + 197, + 66, + 20, + 15, + 18, + 20, + 22, + 16, + 17, + 18, + 16, + 24, + 15, + 21, + 16, + 22, + 26, + 17, + 15, + 25, + 26, + 24, + 11, + 17, + 13, + 18, + 22, + 109, + 83, + 54, + 15, + 17, + 14, + 31, + 20, + 33, + 24, + 13, + 18, + 16, + 22, + 14, + 20, + 17, + 17, + 13, + 25, + 16, + 18, + 16, + 13, + 20, + 13, + 20, + 20, + 19, + 46, + 50, + 20, + 19, + 12, + 17, + 23, + 25, + 15, + 30, + 20, + 14, + 19, + 14, + 17, + 21, + 23, + 11, + 21, + 19, + 21, + 17, + 16, + 19, + 9, + 13, + 17, + 15, + 17, + 13, + 26, + 17, + 22, + 12, + 18, + 14, + 21, + 17, + 29, + 17, + 21, + 26, + 15, + 22, + 28, + 16, + 12, + 27, + 21, + 19, + 27, + 23, + 19, + 21, + 13, + 18, + 20, + 31, + 21, + 43, + 98, + 53, + 23, + 19, + 15, + 16, + 6, + 16, + 21, + 13, + 17, + 20, + 20, + 21, + 17, + 17, + 18, + 17, + 17, + 21, + 11, + 15, + 20, + 20, + 19, + 18, + 19, + 26, + 22, + 100, + 144, + 27, + 39, + 23, + 21, + 18, + 18, + 31, + 24, + 24, + 13, + 18, + 21, + 23, + 14, + 15, + 18, + 23, + 22, + 13, + 19, + 19, + 23, + 17, + 19, + 15, + 15, + 28, + 34, + 110, + 153, + 142, + 19, + 18, + 20, + 16, + 22, + 54, + 20, + 21, + 26, + 24, + 15, + 23, + 11, + 20, + 15, + 12, + 22, + 21, + 28, + 22, + 16, + 17, + 28, + 20, + 26, + 18, + 27, + 36, + 21, + 13, + 21, + 15, + 23, + 19, + 22, + 23, + 17, + 18, + 24, + 19, + 25, + 18, + 12, + 13, + 20, + 7, + 15, + 18, + 15, + 14, + 20, + 18, + 15, + 16, + 20, + 24, + 21, + 27, + 19, + 28, + 32, + 24, + 15, + 15, + 57, + 29, + 20, + 16, + 21, + 16, + 23, + 21, + 17, + 20, + 17, + 23, + 18, + 17, + 20, + 20, + 27, + 22, + 17, + 15, + 16, + 69, + 74, + 52, + 49, + 27, + 21, + 20, + 15, + 18, + 21, + 17, + 25, + 15, + 21, + 19, + 17, + 19, + 16, + 22, + 24, + 23, + 20, + 22, + 16, + 23, + 18, + 26, + 12, + 19, + 18, + 36, + 20, + 26, + 14, + 21, + 30, + 16, + 20, + 25, + 20, + 20, + 23, + 20, + 14, + 20, + 21, + 13, + 24, + 16, + 21, + 26, + 21, + 19, + 15, + 14, + 17, + 17, + 17, + 26, + 17, + 61, + 29, + 20, + 11, + 16, + 18, + 21, + 26, + 18, + 18, + 19, + 24, + 30, + 23, + 19, + 23, + 17, + 21, + 19, + 12, + 14, + 13, + 14, + 17, + 19, + 14, + 17, + 23, + 27, + 100, + 29, + 16, + 20, + 22, + 24, + 13, + 16, + 23, + 19, + 16, + 21, + 12, + 16, + 19, + 22, + 17, + 13, + 15, + 19, + 21, + 24, + 16, + 31, + 32, + 22, + 15, + 16, + 18, + 42, + 83, + 31, + 22, + 22, + 46, + 21, + 17, + 15, + 24, + 17, + 22, + 20, + 23, + 28, + 23, + 21, + 16, + 27, + 23, + 26, + 23, + 25, + 17, + 23, + 19, + 16, + 17, + 16, + 17, + 45, + 23, + 18, + 19, + 22, + 18, + 21, + 20, + 45, + 25, + 19, + 27, + 30, + 19, + 20, + 17, + 15, + 10, + 22, + 23, + 15, + 21, + 16, + 23, + 20, + 20, + 17, + 19, + 13, + 48, + 41, + 27, + 13, + 20, + 13, + 15, + 22, + 15, + 17, + 19, + 13, + 22, + 20, + 18, + 17, + 19, + 21, + 21, + 21, + 27, + 12, + 21, + 9, + 15, + 18, + 29, + 23, + 17, + 15, + 27, + 20, + 19, + 15, + 17, + 20, + 16, + 21, + 26, + 12, + 25, + 26, + 16, + 18, + 17, + 24, + 19, + 14, + 10, + 28, + 19, + 22, + 24, + 13, + 18, + 19, + 18, + 17, + 22, + 28, + 43, + 24, + 18, + 17, + 17, + 17, + 16, + 15, + 18, + 20, + 23, + 26, + 17, + 20, + 15, + 15, + 12, + 15, + 21, + 17, + 14, + 12, + 28, + 17, + 14, + 17, + 26, + 18, + 14, + 64, + 20, + 13, + 15, + 13, + 22, + 18, + 30, + 31, + 8, + 17, + 21, + 23, + 14, + 17, + 12, + 18, + 18, + 18, + 19, + 15, + 9, + 23, + 20, + 19, + 19, + 23, + 18, + 19, + 72, + 27, + 21, + 21, + 19, + 20, + 19, + 21, + 10, + 26, + 16, + 17, + 19, + 16, + 19, + 24, + 17, + 22, + 20, + 15, + 14, + 13, + 22, + 14, + 15, + 18, + 18, + 18, + 18, + 17, + 46, + 32, + 11, + 21, + 19, + 13, + 18, + 26, + 28, + 21, + 25, + 25, + 16, + 16, + 30, + 35, + 13, + 17, + 18, + 26, + 15, + 27, + 23, + 25, + 19, + 20, + 19, + 15, + 19, + 48, + 38, + 32, + 17, + 17, + 20, + 18, + 25, + 24, + 35, + 18, + 20, + 14, + 22, + 19, + 26, + 21, + 21, + 17, + 25, + 20, + 26, + 23, + 18, + 15, + 26, + 23, + 19, + 20, + 14, + 38, + 44, + 36, + 24, + 21, + 31, + 23, + 22, + 43, + 23, + 18, + 21, + 20, + 22, + 44, + 17, + 19, + 23, + 20, + 30, + 17, + 21, + 24, + 22, + 35, + 25, + 20, + 21, + 22, + 29, + 93, + 31, + 25, + 32, + 24, + 31, + 25, + 12, + 43, + 22, + 32, + 37, + 28, + 19, + 20, + 19, + 23, + 31, + 18, + 21, + 18, + 35, + 21, + 27, + 23, + 22, + 28, + 31, + 27, + 49, + 34, + 27, + 25, + 27, + 16, + 17, + 28, + 26, + 55, + 25, + 22, + 24, + 19, + 31, + 19, + 21, + 20, + 18, + 28, + 28, + 20, + 31, + 17, + 22, + 20, + 17, + 30, + 17, + 29, + 47, + 35, + 55, + 26, + 28, + 19, + 26, + 22, + 55, + 24, + 25, + 25, + 31, + 27, + 24, + 26, + 20, + 20, + 17, + 19, + 23, + 13, + 24, + 22, + 19, + 17, + 17, + 20, + 18, + 113, + 51, + 25, + 17, + 18, + 27, + 21, + 23, + 27, + 28, + 27, + 19, + 29, + 31, + 24, + 31, + 15, + 17, + 27, + 20, + 22, + 23, + 17, + 25, + 24, + 30, + 19, + 17, + 26, + 71, + 59, + 30, + 24, + 19, + 28, + 24, + 15, + 17, + 17, + 18, + 22, + 20, + 19, + 13, + 19, + 21, + 17, + 22, + 15, + 15, + 16, + 23, + 18, + 20, + 11, + 16, + 17, + 20, + 24, + 76, + 42, + 51, + 17, + 21, + 16, + 8, + 15, + 46, + 13, + 21, + 19, + 22, + 14, + 19, + 18, + 18, + 23, + 18, + 17, + 22, + 20, + 21, + 14, + 16, + 12, + 20, + 16, + 14, + 29, + 90, + 17, + 18, + 17, + 15, + 21, + 18, + 19, + 24, + 19, + 19, + 15, + 16, + 19, + 18, + 25, + 17, + 17, + 16, + 21, + 21, + 19, + 12, + 19, + 19, + 21, + 22, + 20, + 14, + 33, + 24, + 17, + 15, + 20, + 25, + 17, + 24, + 26, + 27, + 21, + 14, + 20, + 26, + 30, + 13, + 19, + 22, + 16, + 19, + 17, + 19, + 26, + 15, + 17, + 20, + 19, + 20, + 20, + 33, + 15, + 24, + 14, + 18, + 20, + 20, + 24, + 18, + 39, + 17, + 19, + 19, + 17, + 20, + 18, + 19, + 16, + 22, + 15, + 21, + 17, + 10, + 19, + 19, + 27, + 15, + 13, + 15, + 19, + 37, + 52, + 14, + 17, + 17, + 18, + 19, + 19, + 27, + 13, + 14, + 15, + 15, + 19, + 14, + 15, + 11, + 15, + 18, + 20, + 18, + 9, + 24, + 18, + 21, + 18, + 11, + 14, + 16, + 13, + 82, + 27, + 51, + 24, + 14, + 19, + 27, + 12, + 16, + 13, + 16, + 21, + 11, + 18, + 18, + 21, + 21, + 12, + 16, + 12, + 13, + 13, + 28, + 10, + 12, + 15, + 23, + 11, + 18, + 24, + 68, + 27, + 21, + 15, + 17, + 15, + 14, + 19, + 39, + 19, + 8, + 20, + 21, + 18, + 10, + 15, + 20, + 11, + 14, + 19, + 11, + 15, + 23, + 28, + 16, + 17, + 11, + 22, + 18, + 67, + 125, + 115, + 73, + 24, + 8, + 19, + 15, + 16, + 16, + 19, + 18, + 14, + 20, + 21, + 20, + 14, + 16, + 11, + 16, + 20, + 11, + 12, + 11, + 17, + 22, + 9, + 14, + 15, + 20, + 78, + 43, + 18, + 15, + 17, + 13, + 19, + 13, + 21, + 13, + 11, + 15, + 17, + 11, + 12, + 12, + 20, + 15, + 14, + 11, + 22, + 10, + 21, + 15, + 13, + 14, + 11, + 20, + 14, + 43, + 28, + 34, + 14, + 13, + 11, + 16, + 16, + 13, + 20, + 14, + 13, + 16, + 22, + 13, + 15, + 19, + 19, + 21, + 22, + 20, + 21, + 18, + 21, + 14, + 21, + 19, + 25, + 18, + 19, + 46, + 16, + 14, + 15, + 13, + 20, + 14, + 17, + 22, + 14, + 18, + 19, + 14, + 17, + 15, + 13, + 17, + 15, + 16, + 20, + 11, + 19, + 18, + 19, + 8, + 13, + 14, + 17, + 11, + 25, + 59, + 21, + 18, + 17, + 15, + 11, + 11, + 12, + 18, + 19, + 10, + 9, + 14, + 18, + 17, + 15, + 26, + 18, + 13, + 13, + 11, + 20, + 19, + 14, + 10, + 15, + 17, + 15, + 14, + 69, + 27, + 16, + 21, + 16, + 17, + 17, + 19, + 18, + 15, + 8, + 15, + 12, + 11, + 18, + 12, + 18, + 11, + 18, + 13, + 21, + 10, + 25, + 21, + 21, + 10, + 11, + 18, + 15, + 21, + 47, + 37, + 54, + 13, + 17, + 18, + 16, + 15, + 40, + 13, + 14, + 17, + 18, + 17, + 15, + 17, + 14, + 25, + 18, + 23, + 13, + 20, + 23, + 17, + 17, + 29, + 11, + 16, + 15, + 39, + 28, + 15, + 9, + 19, + 15, + 13, + 12, + 18, + 17, + 16, + 16, + 29, + 16, + 15, + 17, + 21, + 14, + 16, + 13, + 22, + 24, + 21, + 9, + 18, + 14, + 11, + 15, + 25, + 21, + 47, + 15, + 19, + 18, + 16, + 19, + 16, + 12, + 28, + 19, + 17, + 25, + 19, + 12, + 22, + 15, + 17, + 20, + 14, + 17, + 15, + 19, + 12, + 13, + 10, + 17, + 15, + 20, + 14, + 40, + 19, + 23, + 18, + 19, + 13, + 8, + 12, + 19, + 16, + 22, + 12, + 13, + 14, + 22, + 18, + 15, + 20, + 13, + 16, + 12, + 16, + 27, + 15, + 12, + 23, + 18, + 16, + 15, + 19, + 24, + 34, + 15, + 8, + 12, + 17, + 16, + 12, + 31, + 15, + 17, + 11, + 12, + 15, + 9, + 24, + 20, + 14, + 9, + 19, + 13, + 25, + 19, + 14, + 17, + 10, + 19, + 10, + 15, + 41, + 42, + 75, + 9, + 14, + 14, + 17, + 14, + 19, + 16, + 14, + 15, + 14, + 12, + 14, + 17, + 12, + 15, + 15, + 17, + 16, + 18, + 22, + 22, + 14, + 18, + 12, + 11, + 18, + 13, + 45, + 43, + 18, + 12, + 21, + 17, + 20, + 15, + 27, + 10, + 16, + 24, + 15, + 23, + 26, + 12, + 16, + 18, + 22, + 21, + 7, + 17, + 9, + 11, + 14, + 14, + 15, + 21, + 9, + 23, + 17, + 19, + 14, + 13, + 19, + 13, + 24, + 17, + 39, + 15, + 24, + 20, + 18, + 14, + 12, + 14, + 15, + 14, + 14, + 15, + 11, + 11, + 13, + 17, + 22, + 19, + 12, + 22, + 14, + 23, + 49, + 21, + 14, + 11, + 16, + 13, + 14, + 44, + 19, + 19, + 17, + 12, + 22, + 20, + 14, + 13, + 13, + 14, + 14, + 21, + 16, + 18, + 18, + 13, + 14, + 9, + 24, + 20, + 14, + 120, + 146, + 92, + 12, + 15, + 17, + 12, + 17, + 38, + 18, + 18, + 11, + 15, + 16, + 12, + 11, + 14, + 17, + 14, + 18, + 13, + 19, + 13, + 13, + 16, + 14, + 19, + 12, + 15, + 33, + 75, + 29, + 26, + 13, + 17, + 14, + 14, + 13, + 13, + 17, + 18, + 9, + 19, + 19, + 10, + 12, + 14, + 10, + 21, + 17, + 14, + 20, + 17, + 11, + 17, + 14, + 20, + 13, + 21, + 109, + 129, + 140, + 21, + 22, + 11, + 21, + 11, + 27, + 16, + 21, + 14, + 16, + 17, + 22, + 16, + 21, + 23, + 17, + 20, + 12, + 19, + 23, + 8, + 16, + 20, + 24, + 19, + 22, + 31, + 27, + 40, + 13, + 15, + 22, + 16, + 16, + 18, + 20, + 19, + 17, + 22, + 24, + 18, + 16, + 14, + 14, + 18, + 16, + 20, + 20, + 19, + 17, + 14, + 16, + 13, + 20, + 20, + 23, + 27, + 39, + 33, + 16, + 5, + 25, + 22, + 20, + 32, + 18, + 18, + 19, + 13, + 15, + 23, + 16, + 26, + 21, + 16, + 20, + 21, + 19, + 21, + 18, + 16, + 21, + 25, + 24, + 17, + 20, + 39, + 19, + 20, + 16, + 14, + 23, + 25, + 20, + 37, + 20, + 20, + 21, + 19, + 18, + 24, + 21, + 20, + 21, + 20, + 19, + 22, + 25, + 17, + 12, + 14, + 17, + 19, + 21, + 15, + 60, + 66, + 20, + 18, + 23, + 30, + 20, + 20, + 37, + 15, + 24, + 18, + 19, + 24, + 21, + 18, + 21, + 17, + 27, + 20, + 17, + 18, + 17, + 20, + 18, + 25, + 14, + 21, + 18, + 24, + 68, + 54, + 30, + 24, + 25, + 20, + 20, + 21, + 60, + 21, + 25, + 29, + 23, + 21, + 22, + 26, + 22, + 19, + 16, + 19, + 22, + 36, + 22, + 22, + 13, + 19, + 27, + 21, + 20, + 87, + 126, + 54, + 21, + 23, + 20, + 28, + 13, + 33, + 25, + 21, + 20, + 13, + 20, + 19, + 13, + 23, + 12, + 25, + 17, + 14, + 24, + 17, + 12, + 13, + 16, + 24, + 16, + 19, + 18, + 66, + 29, + 23, + 24, + 15, + 19, + 17, + 17, + 22, + 19, + 17, + 26, + 28, + 15, + 17, + 28, + 20, + 24, + 18, + 20, + 24, + 24, + 15, + 17, + 19, + 26, + 19, + 12, + 24, + 82, + 60, + 44, + 25, + 10, + 18, + 24, + 22, + 17, + 27, + 20, + 25, + 16, + 20, + 26, + 20, + 18, + 28, + 20, + 15, + 13, + 18, + 18, + 18, + 28, + 17, + 18, + 24, + 19, + 24, + 39, + 67, + 20, + 20, + 30, + 18, + 20, + 24, + 38, + 28, + 36, + 18, + 19, + 21, + 20, + 23, + 17, + 22, + 21, + 25, + 25, + 20, + 16, + 28, + 25, + 21, + 21, + 26, + 22, + 39, + 34, + 25, + 23, + 26, + 21, + 17, + 17, + 20, + 28, + 25, + 21, + 25, + 19, + 35, + 21, + 20, + 20, + 24, + 19, + 24, + 20, + 21, + 20, + 18, + 20, + 17, + 17, + 22, + 22, + 60, + 93, + 68, + 13, + 15, + 19, + 26, + 20, + 37, + 27, + 19, + 22, + 21, + 21, + 19, + 23, + 21, + 16, + 18, + 13, + 23, + 26, + 21, + 18, + 18, + 13, + 12, + 20, + 13, + 37, + 47, + 30, + 20, + 21, + 11, + 22, + 23, + 17, + 26, + 21, + 9, + 17, + 19, + 10, + 22, + 21, + 13, + 23, + 15, + 15, + 21, + 22, + 16, + 12, + 12, + 17, + 14, + 18, + 14, + 25, + 21, + 21, + 13, + 26, + 17, + 21, + 23, + 47, + 19, + 18, + 26, + 10, + 21, + 24, + 11, + 25, + 22, + 13, + 18, + 30, + 28, + 16, + 21, + 20, + 13, + 13, + 17, + 15, + 71, + 88, + 45, + 23, + 21, + 15, + 16, + 20, + 19, + 52, + 22, + 17, + 18, + 12, + 21, + 23, + 12, + 16, + 19, + 14, + 10, + 10, + 17, + 17, + 13, + 15, + 21, + 18, + 12, + 12, + 37, + 24, + 20, + 21, + 21, + 13, + 19, + 23, + 25, + 19, + 17, + 20, + 21, + 13, + 15, + 21, + 19, + 23, + 14, + 13, + 12, + 16, + 18, + 16, + 20, + 16, + 19, + 17, + 15, + 22, + 60, + 80, + 112, + 20, + 24, + 15, + 11, + 21, + 21, + 15, + 24, + 15, + 24, + 18, + 18, + 18, + 16, + 21, + 18, + 15, + 10, + 16, + 19, + 19, + 23, + 18, + 15, + 16, + 20, + 35, + 137, + 45, + 14, + 16, + 25, + 17, + 14, + 26, + 16, + 15, + 19, + 11, + 15, + 13, + 18, + 11, + 14, + 15, + 10, + 16, + 15, + 13, + 15, + 10, + 10, + 15, + 13, + 9, + 24, + 7, + 17, + 17, + 14, + 8, + 11, + 15, + 9, + 24, + 14, + 17, + 11, + 9, + 12, + 12, + 17, + 10, + 18, + 18, + 20, + 15, + 12, + 15, + 19, + 19, + 16, + 18, + 13, + 14, + 27, + 37, + 14, + 11, + 15, + 20, + 11, + 14, + 18, + 13, + 15, + 10, + 10, + 19, + 14, + 17, + 12, + 11, + 18, + 4, + 19, + 12, + 11, + 11, + 14, + 14, + 5, + 15, + 15, + 16, + 24, + 16, + 18, + 14, + 13, + 13, + 19, + 17, + 35, + 14, + 16, + 14, + 11, + 15, + 17, + 11, + 18, + 17, + 12, + 7, + 21, + 14, + 20, + 11, + 15, + 15, + 14, + 14, + 18, + 22, + 72, + 36, + 10, + 16, + 17, + 13, + 17, + 8, + 20, + 15, + 11, + 17, + 16, + 8, + 14, + 14, + 14, + 11, + 11, + 12, + 14, + 15, + 16, + 14, + 16, + 17, + 13, + 15, + 16, + 18, + 95, + 25, + 11, + 9, + 12, + 16, + 14, + 29, + 17, + 14, + 17, + 16, + 7, + 7, + 7, + 9, + 16, + 12, + 9, + 12, + 14, + 20, + 10, + 19, + 11, + 20, + 10, + 17, + 17, + 24, + 13, + 13, + 13, + 10, + 16, + 14, + 18, + 29, + 25, + 15, + 10, + 13, + 10, + 9, + 23, + 13, + 19, + 9, + 21, + 13, + 18, + 20, + 21, + 9, + 17, + 16, + 16, + 28, + 36, + 23, + 37, + 25, + 17, + 13, + 22, + 29, + 10, + 22, + 6, + 12, + 28, + 16, + 18, + 16, + 15, + 6, + 13, + 27, + 20, + 12, + 15, + 24, + 14, + 15, + 20, + 24, + 13, + 12, + 40, + 40, + 25, + 22, + 23, + 17, + 20, + 12, + 24, + 24, + 18, + 16, + 15, + 20, + 31, + 21, + 15, + 24, + 22, + 24, + 24, + 18, + 29, + 18, + 20, + 20, + 30, + 20, + 22, + 18, + 47, + 19, + 24, + 25, + 19, + 27, + 20, + 21, + 25, + 18, + 19, + 18, + 18, + 27, + 15, + 24, + 27, + 28, + 16, + 18, + 15, + 17, + 18, + 25, + 16, + 23, + 25, + 24, + 16, + 26, + 64, + 19, + 27, + 15, + 18, + 17, + 19, + 9, + 27, + 9, + 36, + 22, + 17, + 14, + 22, + 17, + 21, + 25, + 16, + 12, + 16, + 20, + 19, + 16, + 20, + 17, + 22, + 15, + 20, + 58, + 71, + 16, + 13, + 24, + 16, + 15, + 18, + 19, + 24, + 17, + 18, + 20, + 12, + 22, + 17, + 18, + 16, + 16, + 19, + 11, + 19, + 16, + 15, + 22, + 24, + 16, + 17, + 16, + 14, + 75, + 69, + 37, + 14, + 21, + 17, + 24, + 20, + 20, + 19, + 26, + 29, + 11, + 17, + 23, + 17, + 22, + 15, + 16, + 18, + 23, + 21, + 15, + 17, + 15, + 18, + 20, + 21, + 16, + 21, + 19, + 27, + 16, + 16, + 17, + 25, + 8, + 16, + 28, + 15, + 12, + 16, + 13, + 18, + 28, + 14, + 25, + 15, + 15, + 16, + 16, + 19, + 13, + 19, + 15, + 18, + 18, + 15, + 16, + 43, + 24, + 15, + 22, + 20, + 23, + 22, + 21, + 26, + 23, + 17, + 19, + 10, + 23, + 28, + 25, + 19, + 14, + 17, + 16, + 14, + 21, + 15, + 15, + 20, + 17, + 19, + 17, + 17, + 24, + 118, + 76, + 31, + 22, + 15, + 10, + 22, + 20, + 41, + 17, + 17, + 18, + 20, + 20, + 18, + 14, + 24, + 14, + 16, + 13, + 16, + 24, + 20, + 20, + 13, + 25, + 16, + 21, + 20, + 38, + 25, + 26, + 20, + 26, + 24, + 15, + 10, + 13, + 26, + 24, + 21, + 19, + 22, + 25, + 16, + 20, + 21, + 16, + 17, + 25, + 19, + 14, + 17, + 17, + 24, + 22, + 20, + 20, + 17, + 81, + 144, + 17, + 24, + 20, + 20, + 15, + 26, + 29, + 20, + 19, + 15, + 6, + 14, + 14, + 13, + 12, + 16, + 17, + 22, + 18, + 15, + 23, + 26, + 19, + 24, + 22, + 16, + 15, + 22, + 25, + 27, + 28, + 17, + 16, + 20, + 18, + 17, + 19, + 16, + 14, + 9, + 12, + 20, + 20, + 12, + 28, + 16, + 16, + 10, + 19, + 26, + 21, + 12, + 27, + 19, + 24, + 18, + 17, + 43, + 18, + 23, + 13, + 20, + 15, + 15, + 17, + 15, + 21, + 20, + 23, + 13, + 18, + 14, + 16, + 19, + 13, + 13, + 11, + 16, + 15, + 18, + 22, + 5, + 16, + 21, + 14, + 25, + 19, + 50, + 27, + 21, + 23, + 17, + 25, + 12, + 19, + 33, + 19, + 20, + 22, + 16, + 22, + 19, + 18, + 12, + 21, + 26, + 15, + 13, + 16, + 20, + 23, + 18, + 19, + 22, + 12, + 15, + 28, + 18, + 35, + 22, + 19, + 20, + 22, + 15, + 21, + 26, + 19, + 19, + 17, + 25, + 19, + 25, + 25, + 15, + 20, + 25, + 16, + 15, + 11, + 15, + 12, + 19, + 16, + 14, + 21, + 18, + 72, + 96, + 21, + 20, + 6, + 16, + 22, + 24, + 12, + 19, + 18, + 21, + 20, + 19, + 17, + 20, + 28, + 22, + 27, + 28, + 25, + 19, + 23, + 24, + 22, + 23, + 17, + 22, + 17, + 21, + 115, + 176, + 182, + 110, + 18, + 20, + 15, + 17, + 48, + 26, + 19, + 14, + 22, + 20, + 17, + 21, + 27, + 19, + 18, + 19, + 27, + 18, + 25, + 12, + 21, + 26, + 19, + 18, + 22, + 47, + 120, + 82, + 25, + 22, + 19, + 17, + 16, + 20, + 59, + 15, + 22, + 24, + 6, + 27, + 24, + 34, + 30, + 18, + 27, + 25, + 16, + 19, + 24, + 31, + 20, + 22, + 19, + 21, + 12, + 46, + 27, + 47, + 18, + 16, + 12, + 20, + 23, + 25, + 20, + 12, + 24, + 19, + 29, + 16, + 17, + 24, + 22, + 22, + 13, + 24, + 18, + 27, + 23, + 26, + 8, + 22, + 23, + 26, + 31, + 102, + 73, + 23, + 13, + 24, + 28, + 20, + 19, + 43, + 16, + 17, + 21, + 24, + 22, + 34, + 23, + 21, + 21, + 18, + 15, + 10, + 19, + 24, + 23, + 21, + 18, + 22, + 22, + 17, + 34, + 20, + 36, + 20, + 22, + 19, + 19, + 17, + 19, + 15, + 18, + 14, + 18, + 24, + 23, + 23, + 11, + 22, + 20, + 18, + 26, + 24, + 16, + 19, + 20, + 22, + 18, + 16, + 22, + 18, + 75, + 19, + 31, + 20, + 15, + 28, + 22, + 21, + 64, + 13, + 24, + 21, + 15, + 18, + 22, + 12, + 21, + 18, + 20, + 21, + 23, + 25, + 22, + 10, + 16, + 23, + 18, + 24, + 10, + 62, + 46, + 24, + 16, + 10, + 18, + 17, + 26, + 20, + 27, + 15, + 20, + 23, + 18, + 27, + 19, + 18, + 17, + 19, + 16, + 25, + 25, + 30, + 19, + 13, + 20, + 15, + 19, + 18, + 28, + 48, + 25, + 19, + 17, + 19, + 17, + 17, + 17, + 43, + 24, + 22, + 24, + 18, + 24, + 21, + 23, + 12, + 22, + 21, + 20, + 14, + 14, + 20, + 19, + 28, + 14, + 16, + 17, + 24, + 29, + 135, + 108, + 24, + 21, + 20, + 22, + 13, + 21, + 45, + 31, + 23, + 22, + 15, + 18, + 11, + 23, + 19, + 15, + 21, + 15, + 13, + 16, + 27, + 18, + 20, + 13, + 16, + 20, + 16, + 51, + 33, + 17, + 18, + 25, + 18, + 12, + 21, + 13, + 20, + 15, + 20, + 15, + 18, + 23, + 21, + 20, + 19, + 11, + 17, + 19, + 13, + 17, + 20, + 15, + 16, + 14, + 21, + 2, + 15, + 60, + 5, + 23, + 22, + 12, + 18, + 20, + 16, + 19, + 24, + 11, + 20, + 15, + 22, + 17, + 19, + 21, + 25, + 18, + 20, + 19, + 13, + 20, + 20, + 14, + 21, + 15, + 22, + 13, + 37, + 33, + 45, + 19, + 23, + 18, + 18, + 21, + 20, + 14, + 19, + 19, + 17, + 28, + 21, + 16, + 22, + 26, + 24, + 16, + 17, + 13, + 21, + 25, + 20, + 14, + 18, + 13, + 17, + 17, + 68, + 16, + 27, + 17, + 13, + 8, + 14, + 18, + 28, + 17, + 22, + 16, + 15, + 13, + 14, + 18, + 13, + 12, + 19, + 19, + 16, + 16, + 24, + 18, + 11, + 20, + 14, + 18, + 16, + 21, + 16, + 31, + 13, + 17, + 18, + 20, + 17, + 20, + 20, + 29, + 16, + 18, + 14, + 12, + 17, + 14, + 13, + 15, + 12, + 18, + 18, + 12, + 18, + 19, + 23, + 25, + 12, + 18, + 13, + 48, + 21, + 2, + 15, + 14, + 23, + 18, + 11, + 22, + 17, + 11, + 19, + 23, + 24, + 20, + 15, + 19, + 22, + 21, + 17, + 23, + 19, + 18, + 23, + 15, + 21, + 11, + 18, + 13, + 20, + 46, + 25, + 23, + 29, + 22, + 21, + 15, + 20, + 34, + 16, + 21, + 21, + 16, + 18, + 20, + 26, + 23, + 20, + 15, + 22, + 17, + 17, + 17, + 18, + 12, + 19, + 15, + 17, + 14, + 29, + 108, + 37, + 18, + 25, + 14, + 23, + 19, + 23, + 28, + 18, + 21, + 20, + 21, + 25, + 14, + 19, + 26, + 18, + 18, + 28, + 17, + 17, + 14, + 23, + 22, + 17, + 20, + 16, + 19, + 55, + 23, + 29, + 22, + 22, + 19, + 22, + 24, + 30, + 27, + 17, + 18, + 19, + 22, + 16, + 16, + 16, + 12, + 16, + 23, + 19, + 12, + 26, + 21, + 20, + 17, + 18, + 23, + 22, + 21, + 94, + 35, + 23, + 15, + 20, + 17, + 23, + 21, + 19, + 26, + 19, + 17, + 22, + 20, + 22, + 18, + 9, + 22, + 19, + 19, + 19, + 17, + 19, + 12, + 21, + 21, + 12, + 21, + 23, + 50, + 54, + 26, + 20, + 17, + 19, + 23, + 23, + 18, + 11, + 20, + 10, + 18, + 17, + 15, + 18, + 28, + 16, + 16, + 16, + 20, + 27, + 26, + 22, + 27, + 21, + 15, + 23, + 14, + 28, + 124, + 95, + 20, + 20, + 19, + 23, + 14, + 24, + 51, + 19, + 20, + 27, + 22, + 18, + 21, + 17, + 21, + 15, + 23, + 16, + 16, + 14, + 18, + 12, + 17, + 19, + 22, + 16, + 17, + 33, + 59, + 34, + 21, + 15, + 27, + 22, + 16, + 26, + 22, + 14, + 22, + 11, + 19, + 12, + 24, + 15, + 17, + 20, + 18, + 22, + 14, + 20, + 15, + 20, + 21, + 24, + 22, + 17, + 22, + 25, + 21, + 25, + 21, + 17, + 12, + 21, + 25, + 33, + 31, + 33, + 22, + 22, + 19, + 17, + 15, + 19, + 23, + 22, + 26, + 18, + 13, + 21, + 17, + 25, + 25, + 24, + 18, + 22, + 25, + 75, + 34, + 25, + 14, + 24, + 15, + 18, + 20, + 28, + 18, + 18, + 21, + 26, + 14, + 12, + 18, + 25, + 20, + 22, + 26, + 20, + 17, + 22, + 14, + 19, + 22, + 22, + 16, + 24, + 75, + 102, + 21, + 12, + 20, + 16, + 11, + 23, + 21, + 20, + 23, + 14, + 25, + 28, + 15, + 20, + 19, + 15, + 24, + 21, + 15, + 22, + 25, + 17, + 11, + 17, + 22, + 19, + 16, + 18, + 63, + 45, + 16, + 20, + 25, + 18, + 24, + 22, + 32, + 19, + 16, + 21, + 18, + 17, + 13, + 17, + 13, + 21, + 20, + 22, + 24, + 18, + 13, + 24, + 21, + 18, + 29, + 19, + 27, + 34, + 19, + 29, + 22, + 19, + 27, + 30, + 11, + 20, + 18, + 19, + 25, + 17, + 22, + 19, + 21, + 12, + 16, + 23, + 13, + 15, + 18, + 14, + 28, + 17, + 18, + 16, + 23, + 29, + 28, + 79, + 17, + 33, + 27, + 25, + 23, + 13, + 26, + 26, + 25, + 25, + 22, + 17, + 17, + 32, + 21, + 15, + 20, + 18, + 16, + 16, + 16, + 15, + 12, + 16, + 23, + 24, + 20, + 29, + 16, + 46, + 32, + 18, + 13, + 22, + 15, + 18, + 15, + 5, + 19, + 17, + 20, + 14, + 19, + 22, + 16, + 17, + 13, + 18, + 18, + 16, + 14, + 23, + 14, + 13, + 19, + 20, + 16, + 15, + 94, + 53, + 22, + 14, + 19, + 20, + 19, + 25, + 16, + 58, + 22, + 26, + 22, + 25, + 19, + 15, + 17, + 20, + 20, + 23, + 19, + 11, + 19, + 17, + 25, + 18, + 19, + 13, + 23, + 25, + 46, + 23, + 23, + 19, + 20, + 20, + 20, + 15, + 37, + 14, + 14, + 15, + 23, + 17, + 22, + 19, + 18, + 21, + 22, + 18, + 21, + 17, + 18, + 16, + 25, + 11, + 18, + 15, + 18, + 47, + 30, + 36, + 22, + 24, + 22, + 19, + 12, + 18, + 30, + 13, + 18, + 21, + 16, + 18, + 19, + 14, + 22, + 20, + 14, + 19, + 19, + 27, + 21, + 17, + 10, + 18, + 26, + 18, + 23, + 80, + 163, + 74, + 15, + 17, + 18, + 17, + 26, + 59, + 20, + 19, + 24, + 21, + 25, + 23, + 18, + 17, + 17, + 16, + 18, + 20, + 17, + 22, + 17, + 20, + 12, + 21, + 25, + 20, + 37, + 105, + 119, + 15, + 20, + 24, + 18, + 23, + 24, + 63, + 64, + 36, + 23, + 20, + 19, + 14, + 17, + 13, + 19, + 19, + 21, + 15, + 17, + 13, + 11, + 18, + 16, + 21, + 15, + 18, + 60, + 32, + 28, + 22, + 20, + 15, + 21, + 20, + 24, + 15, + 15, + 21, + 19, + 20, + 20, + 14, + 15, + 17, + 25, + 16, + 29, + 19, + 16, + 19, + 26, + 14, + 21, + 17, + 14, + 22, + 112, + 34, + 19, + 15, + 14, + 17, + 20, + 15, + 26, + 19, + 11, + 15, + 16, + 28, + 15, + 18, + 17, + 23, + 13, + 10, + 18, + 14, + 26, + 19, + 12, + 15, + 15, + 21, + 19, + 37, + 22, + 22, + 22, + 20, + 20, + 17, + 21, + 34, + 22, + 18, + 16, + 13, + 20, + 16, + 17, + 18, + 20, + 14, + 20, + 21, + 17, + 14, + 16, + 17, + 20, + 20, + 22, + 19, + 13, + 66, + 35, + 24, + 24, + 19, + 16, + 18, + 19, + 30, + 21, + 22, + 21, + 23, + 24, + 18, + 17, + 24, + 23, + 17, + 12, + 17, + 19, + 20, + 21, + 16, + 15, + 25, + 25, + 16, + 34, + 40, + 34, + 23, + 18, + 15, + 14, + 18, + 17, + 65, + 19, + 24, + 27, + 18, + 21, + 26, + 19, + 23, + 24, + 22, + 18, + 22, + 17, + 21, + 17, + 21, + 16, + 14, + 16, + 17, + 51, + 19, + 25, + 14, + 4, + 17, + 16, + 17, + 23, + 18, + 17, + 19, + 16, + 17, + 22, + 22, + 20, + 22, + 12, + 15, + 15, + 10, + 25, + 13, + 9, + 11, + 20, + 18, + 22, + 21, + 64, + 32, + 24, + 22, + 13, + 20, + 20, + 18, + 39, + 20, + 18, + 18, + 28, + 13, + 17, + 20, + 18, + 19, + 13, + 16, + 16, + 21, + 16, + 18, + 16, + 22, + 24, + 22, + 13, + 57, + 5, + 46, + 22, + 16, + 15, + 15, + 13, + 19, + 50, + 19, + 16, + 16, + 19, + 21, + 18, + 16, + 11, + 17, + 20, + 21, + 21, + 17, + 25, + 15, + 14, + 18, + 18, + 20, + 20, + 60, + 18, + 25, + 19, + 22, + 17, + 12, + 19, + 21, + 16, + 20, + 17, + 17, + 19, + 14, + 22, + 17, + 15, + 22, + 14, + 22, + 28, + 15, + 14, + 24, + 25, + 12, + 10, + 20, + 32, + 74, + 25, + 19, + 24, + 13, + 19, + 18, + 16, + 33, + 14, + 9, + 22, + 18, + 19, + 18, + 22, + 19, + 20, + 18, + 14, + 20, + 25, + 16, + 19, + 15, + 23, + 14, + 19, + 18, + 70, + 75, + 26, + 18, + 20, + 13, + 17, + 19, + 30, + 17, + 16, + 18, + 21, + 16, + 22, + 22, + 16, + 15, + 18, + 15, + 21, + 17, + 22, + 14, + 21, + 22, + 21, + 23, + 15, + 22, + 52, + 43, + 33, + 19, + 20, + 16, + 14, + 14, + 21, + 16, + 20, + 13, + 14, + 15, + 17, + 18, + 17, + 21, + 14, + 19, + 15, + 24, + 24, + 9, + 18, + 18, + 20, + 12, + 12, + 56, + 26, + 23, + 18, + 18, + 20, + 20, + 20, + 15, + 50, + 18, + 23, + 23, + 13, + 16, + 23, + 19, + 16, + 20, + 12, + 15, + 13, + 26, + 19, + 15, + 15, + 18, + 23, + 19, + 14, + 31, + 76, + 14, + 16, + 21, + 20, + 15, + 15, + 25, + 26, + 21, + 17, + 21, + 18, + 24, + 20, + 17, + 16, + 20, + 19, + 20, + 18, + 26, + 18, + 14, + 27, + 18, + 21, + 28, + 22, + 112, + 101, + 31, + 22, + 17, + 27, + 15, + 15, + 27, + 13, + 16, + 14, + 18, + 20, + 19, + 24, + 19, + 17, + 14, + 20, + 13, + 20, + 19, + 13, + 20, + 21, + 15, + 12, + 14, + 51, + 25, + 19, + 20, + 22, + 24, + 24, + 18, + 75, + 29, + 13, + 19, + 26, + 23, + 18, + 9, + 10, + 13, + 9, + 19, + 23, + 22, + 16, + 18, + 12, + 15, + 18, + 19, + 20, + 35, + 33, + 14, + 14, + 19, + 17, + 20, + 17, + 20, + 20, + 19, + 16, + 19, + 15, + 19, + 20, + 24, + 20, + 21, + 23, + 19, + 15, + 23, + 18, + 19, + 13, + 20, + 16, + 30, + 9, + 60, + 26, + 23, + 11, + 15, + 16, + 21, + 15, + 17, + 17, + 17, + 15, + 16, + 23, + 22, + 15, + 18, + 13, + 13, + 21, + 12, + 11, + 21, + 17, + 23, + 21, + 19, + 21, + 15, + 43, + 112, + 156, + 100, + 19, + 22, + 23, + 18, + 22, + 30, + 20, + 15, + 16, + 15, + 24, + 20, + 15, + 19, + 15, + 15, + 19, + 15, + 20, + 20, + 18, + 13, + 18, + 16, + 7, + 18, + 76, + 22, + 25, + 23, + 18, + 21, + 19, + 19, + 26, + 16, + 11, + 17, + 16, + 15, + 8, + 23, + 14, + 18, + 19, + 15, + 22, + 13, + 15, + 13, + 23, + 18, + 14, + 31, + 28, + 35, + 36, + 34, + 24, + 12, + 18, + 20, + 12, + 15, + 28, + 9, + 9, + 17, + 10, + 23, + 15, + 15, + 12, + 13, + 17, + 16, + 19, + 18, + 17, + 14, + 22, + 19, + 14, + 23, + 16, + 33, + 29, + 25, + 16, + 21, + 13, + 13, + 16, + 33, + 22, + 19, + 22, + 24, + 18, + 20, + 17, + 17, + 15, + 19, + 22, + 20, + 20, + 16, + 17, + 16, + 20, + 14, + 16, + 15, + 23, + 52, + 102, + 15, + 12, + 17, + 23, + 25, + 14, + 26, + 21, + 19, + 22, + 12, + 13, + 25, + 22, + 29, + 17, + 15, + 13, + 19, + 19, + 16, + 13, + 14, + 18, + 12, + 18, + 14, + 55, + 42, + 46, + 15, + 14, + 16, + 12, + 13, + 11, + 20, + 22, + 20, + 13, + 16, + 13, + 20, + 18, + 14, + 18, + 20, + 15, + 24, + 13, + 17, + 16, + 18, + 19, + 22, + 14, + 19, + 41, + 36, + 18, + 9, + 17, + 20, + 12, + 21, + 15, + 25, + 16, + 18, + 20, + 21, + 13, + 19, + 13, + 22, + 20, + 21, + 16, + 18, + 9, + 14, + 18, + 20, + 11, + 20, + 18, + 39, + 27, + 25, + 22, + 20, + 20, + 22, + 14, + 19, + 22, + 19, + 20, + 15, + 24, + 21, + 21, + 21, + 25, + 23, + 17, + 14, + 21, + 16, + 15, + 20, + 17, + 15, + 16, + 22, + 32, + 59, + 17, + 17, + 8, + 20, + 20, + 18, + 24, + 32, + 20, + 14, + 20, + 15, + 16, + 13, + 16, + 25, + 24, + 25, + 19, + 20, + 8, + 15, + 16, + 16, + 17, + 21, + 18, + 19, + 43, + 39, + 24, + 22, + 17, + 26, + 21, + 21, + 20, + 36, + 16, + 13, + 18, + 16, + 15, + 25, + 16, + 23, + 14, + 18, + 22, + 28, + 23, + 16, + 27, + 20, + 23, + 20, + 15, + 18, + 36, + 20, + 16, + 19, + 24, + 18, + 25, + 22, + 25, + 22, + 11, + 13, + 14, + 18, + 20, + 14, + 17, + 16, + 10, + 15, + 16, + 19, + 13, + 17, + 22, + 26, + 21, + 20, + 15, + 48, + 35, + 54, + 14, + 20, + 17, + 15, + 21, + 13, + 27, + 21, + 16, + 16, + 16, + 14, + 16, + 17, + 22, + 15, + 21, + 16, + 12, + 16, + 20, + 14, + 20, + 18, + 17, + 15, + 19, + 48, + 101, + 38, + 26, + 17, + 23, + 17, + 16, + 19, + 17, + 19, + 22, + 18, + 18, + 20, + 19, + 18, + 21, + 18, + 9, + 15, + 12, + 21, + 18, + 18, + 12, + 17, + 19, + 27, + 9, + 55, + 28, + 23, + 13, + 21, + 21, + 27, + 22, + 31, + 11, + 19, + 13, + 14, + 18, + 12, + 20, + 15, + 25, + 14, + 26, + 23, + 20, + 23, + 27, + 22, + 22, + 18, + 23, + 27, + 29, + 73, + 28, + 15, + 18, + 26, + 14, + 19, + 15, + 24, + 19, + 19, + 19, + 20, + 14, + 18, + 22, + 19, + 15, + 17, + 21, + 11, + 18, + 22, + 23, + 20, + 24, + 21, + 28, + 22, + 85, + 37, + 22, + 18, + 20, + 19, + 22, + 15, + 41, + 26, + 14, + 29, + 16, + 16, + 17, + 19, + 19, + 15, + 16, + 13, + 23, + 16, + 21, + 12, + 15, + 17, + 27, + 13, + 16, + 61, + 120, + 101, + 19, + 21, + 16, + 27, + 10, + 14, + 15, + 23, + 24, + 18, + 16, + 16, + 18, + 22, + 14, + 16, + 19, + 15, + 12, + 21, + 17, + 14, + 18, + 18, + 19, + 23, + 20, + 43, + 21, + 24, + 22, + 21, + 16, + 22, + 15, + 28, + 15, + 11, + 17, + 18, + 17, + 16, + 19, + 10, + 15, + 11, + 17, + 18, + 19, + 22, + 14, + 21, + 17, + 23, + 13, + 17, + 56, + 28, + 30, + 13, + 17, + 26, + 18, + 20, + 30, + 18, + 20, + 23, + 12, + 18, + 14, + 21, + 22, + 20, + 16, + 15, + 15, + 23, + 23, + 13, + 17, + 11, + 19, + 22, + 17, + 13, + 117, + 110, + 21, + 22, + 17, + 17, + 18, + 17, + 47, + 17, + 14, + 16, + 18, + 21, + 21, + 17, + 10, + 17, + 15, + 19, + 15, + 19, + 21, + 24, + 20, + 23, + 19, + 23, + 21, + 61, + 147, + 141, + 27, + 18, + 10, + 18, + 18, + 19, + 25, + 17, + 15, + 17, + 17, + 20, + 15, + 12, + 13, + 20, + 22, + 23, + 14, + 25, + 19, + 17, + 20, + 16, + 18, + 25, + 27, + 61, + 15, + 19, + 16, + 18, + 20, + 18, + 13, + 37, + 19, + 13, + 13, + 23, + 19, + 20, + 13, + 16, + 20, + 24, + 17, + 16, + 14, + 17, + 14, + 17, + 19, + 16, + 17, + 12, + 39, + 60, + 29, + 17, + 21, + 15, + 24, + 19, + 18, + 21, + 24, + 13, + 15, + 16, + 23, + 20, + 15, + 23, + 24, + 22, + 12, + 18, + 21, + 20, + 21, + 16, + 20, + 18, + 20, + 16, + 72, + 61, + 34, + 11, + 25, + 19, + 17, + 24, + 33, + 23, + 11, + 21, + 21, + 16, + 17, + 22, + 21, + 14, + 19, + 13, + 17, + 17, + 20, + 24, + 22, + 2, + 7, + 19, + 13, + 35, + 141, + 136, + 18, + 18, + 21, + 20, + 20, + 24, + 23, + 15, + 22, + 13, + 22, + 20, + 23, + 18, + 20, + 17, + 25, + 12, + 16, + 22, + 24, + 22, + 19, + 13, + 19, + 19, + 26, + 50, + 27, + 50, + 32, + 26, + 15, + 14, + 17, + 23, + 15, + 18, + 20, + 20, + 26, + 27, + 25, + 20, + 17, + 14, + 20, + 14, + 15, + 18, + 16, + 12, + 25, + 21, + 13, + 13, + 16, + 109, + 24, + 20, + 16, + 18, + 11, + 14, + 15, + 18, + 17, + 18, + 20, + 18, + 19, + 22, + 19, + 15, + 22, + 22, + 17, + 20, + 18, + 20, + 20, + 19, + 18, + 18, + 17, + 22, + 35, + 61, + 31, + 7, + 19, + 12, + 14, + 18, + 20, + 29, + 18, + 25, + 21, + 11, + 16, + 19, + 21, + 10, + 19, + 15, + 21, + 17, + 19, + 17, + 11, + 19, + 19, + 20, + 22, + 17, + 66, + 99, + 115, + 31, + 19, + 17, + 18, + 16, + 30, + 25, + 16, + 19, + 15, + 21, + 23, + 19, + 18, + 15, + 15, + 22, + 20, + 15, + 24, + 27, + 15, + 20, + 22, + 18, + 14, + 20, + 52, + 19, + 19, + 18, + 19, + 26, + 29, + 15, + 50, + 24, + 18, + 24, + 10, + 25, + 16, + 16, + 18, + 27, + 16, + 19, + 22, + 24, + 21, + 22, + 17, + 20, + 17, + 19, + 17, + 60, + 26, + 16, + 20, + 22, + 17, + 17, + 22, + 20, + 21, + 20, + 15, + 17, + 18, + 17, + 25, + 19, + 15, + 18, + 17, + 16, + 14, + 32, + 27, + 14, + 17, + 19, + 21, + 20, + 20, + 69, + 26, + 21, + 11, + 16, + 14, + 20, + 20, + 25, + 26, + 9, + 13, + 15, + 20, + 21, + 11, + 10, + 16, + 14, + 17, + 22, + 19, + 15, + 17, + 23, + 20, + 15, + 20, + 14, + 28, + 33, + 27, + 17, + 21, + 16, + 23, + 23, + 21, + 23, + 19, + 14, + 13, + 14, + 19, + 15, + 22, + 15, + 19, + 16, + 14, + 16, + 13, + 18, + 16, + 16, + 11, + 10, + 19, + 13, + 47, + 26, + 23, + 23, + 16, + 21, + 19, + 15, + 22, + 20, + 21, + 17, + 25, + 22, + 14, + 22, + 27, + 18, + 21, + 22, + 18, + 19, + 19, + 19, + 25, + 18, + 23, + 22, + 21, + 22, + 75, + 29, + 25, + 21, + 24, + 26, + 20, + 31, + 44, + 23, + 25, + 16, + 16, + 21, + 7, + 19, + 12, + 23, + 18, + 19, + 23, + 21, + 22, + 19, + 13, + 13, + 22, + 21, + 17, + 38, + 64, + 35, + 19, + 17, + 27, + 19, + 12, + 19, + 43, + 17, + 16, + 22, + 18, + 17, + 18, + 19, + 16, + 21, + 19, + 18, + 18, + 21, + 21, + 23, + 17, + 12, + 13, + 20, + 18, + 88, + 100, + 87, + 21, + 26, + 18, + 20, + 21, + 39, + 14, + 19, + 20, + 19, + 24, + 27, + 20, + 21, + 20, + 30, + 20, + 23, + 21, + 26, + 26, + 23, + 17, + 23, + 19, + 23, + 17, + 43, + 54, + 30, + 20, + 20, + 18, + 18, + 29, + 43, + 24, + 16, + 29, + 21, + 22, + 18, + 28, + 18, + 23, + 17, + 16, + 27, + 25, + 14, + 23, + 22, + 24, + 21, + 21, + 27, + 52, + 54, + 15, + 21, + 15, + 23, + 28, + 23, + 41, + 18, + 21, + 14, + 14, + 20, + 21, + 29, + 19, + 18, + 18, + 24, + 16, + 18, + 22, + 17, + 22, + 21, + 22, + 15, + 22, + 23, + 53, + 118, + 96, + 26, + 21, + 21, + 21, + 19, + 26, + 17, + 18, + 24, + 20, + 27, + 21, + 31, + 20, + 16, + 21, + 17, + 14, + 31, + 23, + 10, + 19, + 20, + 10, + 18, + 18, + 41, + 35, + 19, + 26, + 17, + 17, + 13, + 26, + 20, + 31, + 18, + 17, + 31, + 4, + 27, + 21, + 22, + 19, + 13, + 28, + 16, + 21, + 21, + 24, + 17, + 14, + 18, + 18, + 22, + 30, + 84, + 81, + 51, + 19, + 19, + 17, + 17, + 18, + 62, + 44, + 16, + 21, + 20, + 24, + 22, + 15, + 18, + 21, + 20, + 17, + 20, + 23, + 22, + 32, + 19, + 14, + 14, + 16, + 19, + 51, + 101, + 33, + 20, + 16, + 22, + 18, + 19, + 19, + 20, + 20, + 20, + 17, + 17, + 22, + 19, + 12, + 20, + 16, + 13, + 18, + 17, + 14, + 11, + 16, + 25, + 16, + 17, + 20, + 22, + 17, + 21, + 24, + 22, + 8, + 20, + 17, + 20, + 33, + 17, + 25, + 20, + 24, + 19, + 14, + 31, + 15, + 22, + 19, + 19, + 22, + 22, + 4, + 22, + 27, + 21, + 24, + 24, + 18, + 54, + 66, + 44, + 24, + 22, + 22, + 21, + 23, + 19, + 37, + 19, + 24, + 19, + 20, + 18, + 21, + 20, + 16, + 19, + 12, + 25, + 23, + 21, + 15, + 20, + 17, + 18, + 21, + 18, + 14, + 67, + 82, + 23, + 19, + 16, + 14, + 13, + 15, + 22, + 26, + 5, + 18, + 24, + 26, + 20, + 19, + 19, + 15, + 20, + 25, + 14, + 20, + 15, + 17, + 19, + 16, + 19, + 12, + 14, + 16, + 36, + 24, + 14, + 13, + 22, + 19, + 28, + 22, + 61, + 23, + 23, + 24, + 18, + 17, + 11, + 18, + 14, + 21, + 20, + 26, + 22, + 12, + 21, + 13, + 17, + 23, + 17, + 16, + 15, + 30, + 6, + 24, + 17, + 21, + 16, + 19, + 19, + 37, + 74, + 50, + 17, + 23, + 23, + 16, + 13, + 20, + 21, + 25, + 24, + 19, + 16, + 22, + 24, + 19, + 13, + 13, + 26, + 23, + 20, + 41, + 29, + 8, + 13, + 19, + 18, + 15, + 10, + 41, + 18, + 15, + 16, + 13, + 10, + 24, + 15, + 17, + 21, + 19, + 21, + 18, + 15, + 18, + 18, + 25, + 15, + 23, + 18, + 24, + 42, + 61, + 41, + 17, + 25, + 13, + 18, + 18, + 27, + 15, + 16, + 16, + 16, + 13, + 18, + 16, + 18, + 15, + 20, + 30, + 18, + 28, + 22, + 17, + 19, + 23, + 18, + 15, + 16, + 16, + 85, + 117, + 29, + 12, + 17, + 21, + 16, + 21, + 38, + 18, + 11, + 15, + 13, + 16, + 24, + 19, + 18, + 17, + 15, + 18, + 15, + 20, + 21, + 19, + 22, + 23, + 20, + 15, + 22, + 34, + 107, + 58, + 19, + 15, + 28, + 18, + 16, + 10, + 48, + 19, + 15, + 24, + 28, + 14, + 13, + 22, + 19, + 16, + 9, + 24, + 18, + 21, + 17, + 17, + 27, + 18, + 19, + 18, + 17, + 111, + 150, + 95, + 18, + 20, + 16, + 23, + 16, + 33, + 10, + 22, + 22, + 19, + 24, + 23, + 29, + 14, + 23, + 26, + 21, + 22, + 11, + 23, + 18, + 35, + 26, + 14, + 21, + 19, + 18, + 30, + 35, + 8, + 12, + 25, + 15, + 13, + 21, + 20, + 18, + 19, + 15, + 18, + 19, + 22, + 18, + 12, + 24, + 22, + 8, + 32, + 14, + 19, + 20, + 15, + 17, + 21, + 23, + 22, + 36, + 47, + 26, + 23, + 20, + 9, + 21, + 18, + 29, + 20, + 20, + 28, + 25, + 23, + 23, + 19, + 19, + 21, + 22, + 16, + 19, + 20, + 14, + 23, + 14, + 18, + 12, + 18, + 19, + 50, + 146, + 30, + 15, + 18, + 18, + 25, + 16, + 38, + 22, + 17, + 14, + 16, + 21, + 28, + 18, + 20, + 19, + 17, + 15, + 20, + 21, + 16, + 18, + 33, + 14, + 15, + 19, + 15, + 20, + 44, + 12, + 13, + 16, + 20, + 18, + 16, + 12, + 44, + 13, + 24, + 23, + 10, + 21, + 13, + 15, + 13, + 10, + 31, + 17, + 20, + 13, + 18, + 18, + 16, + 18, + 12, + 20, + 11, + 29, + 28, + 42, + 15, + 12, + 16, + 24, + 19, + 15, + 18, + 9, + 20, + 13, + 19, + 12, + 14, + 22, + 16, + 17, + 12, + 12, + 17, + 13, + 13, + 28, + 14, + 14, + 15, + 19, + 15, + 35, + 24, + 17, + 14, + 18, + 15, + 19, + 13, + 52, + 19, + 20, + 17, + 22, + 19, + 21, + 16, + 15, + 11, + 18, + 17, + 14, + 14, + 13, + 16, + 18, + 15, + 15, + 20, + 12, + 21, + 92, + 16, + 10, + 7, + 8, + 14, + 15, + 14, + 42, + 18, + 6, + 13, + 18, + 16, + 14, + 12, + 18, + 11, + 14, + 9, + 18, + 14, + 24, + 17, + 9, + 18, + 20, + 21, + 10, + 47, + 150, + 89, + 19, + 16, + 19, + 28, + 13, + 21, + 19, + 21, + 12, + 15, + 13, + 29, + 19, + 11, + 14, + 15, + 10, + 15, + 16, + 19, + 24, + 21, + 16, + 17, + 14, + 11, + 11, + 30, + 17, + 14, + 18, + 14, + 16, + 11, + 22, + 18, + 12, + 15, + 20, + 15, + 14, + 20, + 17, + 18, + 22, + 15, + 11, + 20, + 15, + 19, + 9, + 13, + 16, + 14, + 23, + 22, + 30, + 34, + 20, + 13, + 22, + 16, + 18, + 21, + 17, + 49, + 11, + 22, + 15, + 11, + 20, + 14, + 11, + 17, + 12, + 13, + 15, + 23, + 12, + 9, + 20, + 15, + 13, + 15, + 14, + 15, + 69, + 28, + 23, + 13, + 14, + 20, + 19, + 19, + 21, + 16, + 11, + 17, + 16, + 13, + 13, + 13, + 15, + 12, + 14, + 11, + 18, + 20, + 11, + 18, + 7, + 16, + 10, + 19, + 14, + 29, + 25, + 16, + 15, + 11, + 9, + 13, + 15, + 15, + 65, + 15, + 18, + 15, + 24, + 18, + 20, + 15, + 18, + 17, + 25, + 25, + 15, + 16, + 16, + 17, + 28, + 18, + 17, + 16, + 13, + 81, + 60, + 68, + 13, + 15, + 16, + 13, + 23, + 16, + 19, + 10, + 18, + 16, + 16, + 21, + 18, + 20, + 20, + 22, + 19, + 9, + 17, + 11, + 17, + 18, + 18, + 11, + 19, + 13, + 14, + 53, + 17, + 20, + 13, + 18, + 3, + 11, + 14, + 17, + 17, + 19, + 21, + 18, + 19, + 20, + 13, + 13, + 18, + 12, + 11, + 21, + 16, + 27, + 15, + 15, + 13, + 15, + 18, + 14, + 65, + 32, + 21, + 17, + 15, + 10, + 15, + 14, + 15, + 19, + 18, + 17, + 17, + 19, + 24, + 21, + 22, + 17, + 11, + 16, + 12, + 13, + 18, + 23, + 22, + 29, + 19, + 10, + 12, + 18, + 94, + 86, + 29, + 13, + 20, + 21, + 16, + 17, + 27, + 20, + 23, + 20, + 17, + 17, + 21, + 19, + 12, + 15, + 24, + 27, + 16, + 21, + 22, + 15, + 19, + 13, + 17, + 17, + 19, + 23, + 37, + 23, + 18, + 10, + 10, + 20, + 18, + 21, + 33, + 21, + 18, + 22, + 23, + 24, + 26, + 22, + 25, + 28, + 22, + 16, + 15, + 21, + 14, + 15, + 21, + 12, + 14, + 21, + 15, + 57, + 49, + 27, + 27, + 27, + 22, + 16, + 26, + 14, + 36, + 23, + 20, + 24, + 23, + 29, + 28, + 18, + 28, + 16, + 23, + 24, + 21, + 16, + 19, + 24, + 19, + 17, + 23, + 22, + 23, + 111, + 25, + 34, + 23, + 17, + 22, + 10, + 29, + 43, + 21, + 12, + 24, + 18, + 16, + 16, + 22, + 19, + 20, + 21, + 13, + 18, + 12, + 14, + 13, + 19, + 15, + 25, + 21, + 23, + 82, + 25, + 17, + 15, + 26, + 18, + 19, + 20, + 15, + 28, + 25, + 22, + 18, + 25, + 14, + 17, + 14, + 18, + 15, + 14, + 12, + 17, + 23, + 16, + 17, + 18, + 19, + 7, + 20, + 18, + 31, + 54, + 20, + 20, + 21, + 11, + 13, + 12, + 41, + 13, + 14, + 21, + 16, + 14, + 17, + 24, + 21, + 23, + 19, + 20, + 15, + 19, + 23, + 15, + 17, + 26, + 22, + 17, + 30, + 39, + 60, + 27, + 15, + 20, + 14, + 14, + 22, + 13, + 57, + 21, + 10, + 23, + 14, + 20, + 21, + 16, + 14, + 14, + 14, + 26, + 21, + 10, + 13, + 20, + 15, + 21, + 19, + 18, + 22, + 64, + 76, + 17, + 20, + 14, + 17, + 19, + 11, + 27, + 18, + 21, + 20, + 10, + 18, + 21, + 26, + 21, + 10, + 17, + 21, + 29, + 33, + 19, + 29, + 26, + 23, + 19, + 23, + 16, + 22, + 70, + 44, + 50, + 20, + 23, + 14, + 20, + 27, + 76, + 31, + 12, + 21, + 12, + 16, + 22, + 15, + 13, + 18, + 12, + 23, + 21, + 13, + 21, + 11, + 14, + 10, + 21, + 19, + 10, + 36, + 32, + 9, + 20, + 12, + 14, + 8, + 9, + 22, + 15, + 17, + 12, + 25, + 13, + 12, + 19, + 18, + 14, + 20, + 17, + 17, + 13, + 18, + 18, + 9, + 17, + 12, + 14, + 19, + 11, + 46, + 14, + 38, + 16, + 15, + 8, + 21, + 28, + 39, + 52, + 21, + 13, + 20, + 13, + 19, + 10, + 14, + 19, + 15, + 16, + 17, + 10, + 17, + 18, + 12, + 22, + 15, + 16, + 25, + 23, + 24, + 29, + 24, + 19, + 18, + 18, + 15, + 15, + 41, + 20, + 30, + 9, + 13, + 14, + 21, + 15, + 18, + 7, + 16, + 10, + 11, + 20, + 14, + 16, + 23, + 12, + 22, + 14, + 26, + 105, + 17, + 26, + 26, + 20, + 20, + 22, + 13, + 36, + 19, + 15, + 24, + 20, + 17, + 23, + 17, + 14, + 28, + 14, + 19, + 18, + 16, + 16, + 22, + 18, + 11, + 19, + 21, + 13, + 33, + 58, + 34, + 21, + 20, + 15, + 18, + 18, + 26, + 26, + 4, + 21, + 21, + 24, + 20, + 10, + 10, + 21, + 21, + 20, + 18, + 20, + 15, + 12, + 25, + 21, + 19, + 20, + 21, + 11, + 77, + 18, + 32, + 12, + 20, + 18, + 22, + 13, + 11, + 12, + 11, + 16, + 18, + 27, + 24, + 21, + 29, + 13, + 26, + 23, + 28, + 20, + 29, + 21, + 14, + 19, + 18, + 13, + 30, + 25, + 92, + 18, + 17, + 15, + 12, + 23, + 21, + 27, + 24, + 21, + 18, + 13, + 8, + 15, + 27, + 19, + 20, + 8, + 16, + 19, + 17, + 16, + 17, + 23, + 16, + 16, + 16, + 19, + 17, + 66, + 52, + 23, + 16, + 17, + 14, + 17, + 13, + 28, + 18, + 14, + 13, + 11, + 19, + 19, + 24, + 17, + 13, + 12, + 17, + 24, + 14, + 18, + 14, + 14, + 13, + 18, + 20, + 11, + 13, + 58, + 23, + 14, + 16, + 16, + 14, + 21, + 14, + 50, + 7, + 8, + 12, + 13, + 23, + 14, + 3, + 18, + 30, + 10, + 21, + 12, + 9, + 13, + 10, + 12, + 21, + 15, + 24, + 12, + 32, + 29, + 18, + 15, + 16, + 15, + 19, + 17, + 10, + 31, + 9, + 13, + 15, + 17, + 12, + 13, + 13, + 10, + 14, + 14, + 7, + 16, + 18, + 14, + 17, + 13, + 13, + 11, + 16, + 17, + 41, + 20, + 20, + 15, + 19, + 16, + 10, + 15, + 27, + 11, + 11, + 27, + 17, + 20, + 15, + 14, + 23, + 18, + 18, + 16, + 13, + 10, + 19, + 18, + 25, + 20, + 12, + 12, + 17, + 15, + 44, + 22, + 14, + 13, + 13, + 14, + 27, + 12, + 17, + 17, + 18, + 9, + 17, + 23, + 26, + 14, + 19, + 14, + 19, + 9, + 7, + 20, + 18, + 19, + 11, + 13, + 15, + 24, + 13, + 37, + 144, + 149, + 40, + 13, + 12, + 11, + 22, + 10, + 26, + 14, + 14, + 13, + 15, + 17, + 18, + 15, + 15, + 15, + 18, + 20, + 13, + 15, + 8, + 17, + 16, + 13, + 13, + 18, + 7, + 29, + 14, + 19, + 17, + 15, + 16, + 11, + 12, + 17, + 19, + 21, + 10, + 22, + 16, + 11, + 13, + 12, + 14, + 8, + 14, + 16, + 11, + 9, + 11, + 28, + 16, + 12, + 16, + 14, + 31, + 52, + 40, + 11, + 20, + 16, + 24, + 17, + 10, + 14, + 10, + 17, + 15, + 12, + 17, + 11, + 19, + 11, + 12, + 16, + 14, + 7, + 12, + 14, + 16, + 16, + 16, + 13, + 16, + 9, + 64, + 88, + 11, + 11, + 11, + 15, + 13, + 14, + 22, + 12, + 10, + 11, + 15, + 10, + 19, + 15, + 13, + 18, + 16, + 15, + 23, + 18, + 16, + 18, + 22, + 20, + 21, + 14, + 20, + 20, + 35, + 40, + 21, + 11, + 11, + 10, + 15, + 15, + 25, + 16, + 23, + 15, + 19, + 12, + 21, + 17, + 11, + 13, + 16, + 20, + 19, + 10, + 16, + 15, + 16, + 19, + 16, + 15, + 14, + 8, + 35, + 18, + 21, + 17, + 22, + 15, + 16, + 17, + 34, + 15, + 17, + 14, + 12, + 23, + 11, + 20, + 16, + 20, + 15, + 23, + 15, + 20, + 12, + 15, + 20, + 19, + 19, + 16, + 18, + 124, + 132, + 16, + 26, + 15, + 19, + 17, + 15, + 23, + 15, + 29, + 26, + 15, + 18, + 24, + 18, + 20, + 14, + 15, + 19, + 11, + 17, + 14, + 18, + 17, + 10, + 18, + 16, + 8, + 41, + 14, + 19, + 14, + 12, + 7, + 9, + 18, + 16, + 18, + 18, + 15, + 20, + 17, + 20, + 13, + 15, + 21, + 15, + 13, + 8, + 27, + 15, + 16, + 12, + 21, + 16, + 16, + 31, + 13, + 49, + 50, + 19, + 27, + 23, + 19, + 11, + 12, + 23, + 11, + 18, + 10, + 7, + 11, + 9, + 20, + 14, + 11, + 10, + 21, + 16, + 14, + 8, + 10, + 14, + 17, + 9, + 8, + 15, + 41, + 49, + 19, + 4, + 8, + 16, + 12, + 14, + 13, + 17, + 16, + 11, + 22, + 19, + 23, + 15, + 22, + 16, + 11, + 21, + 25, + 10, + 20, + 14, + 17, + 14, + 11, + 13, + 11, + 14, + 59, + 18, + 22, + 12, + 19, + 18, + 14, + 16, + 44, + 19, + 31, + 18, + 15, + 12, + 18, + 25, + 15, + 10, + 12, + 21, + 19, + 18, + 18, + 19, + 12, + 20, + 17, + 15, + 18, + 29, + 139, + 143, + 14, + 12, + 8, + 13, + 24, + 15, + 45, + 16, + 4, + 18, + 12, + 18, + 10, + 19, + 29, + 18, + 14, + 23, + 22, + 22, + 15, + 17, + 15, + 19, + 17, + 11, + 14, + 50, + 104, + 58, + 12, + 12, + 17, + 15, + 25, + 30, + 13, + 9, + 10, + 17, + 20, + 16, + 16, + 18, + 16, + 12, + 19, + 15, + 9, + 19, + 11, + 16, + 18, + 14, + 7, + 15, + 11, + 67, + 31, + 41, + 21, + 14, + 7, + 13, + 15, + 17, + 9, + 8, + 12, + 10, + 10, + 16, + 13, + 28, + 7, + 13, + 14, + 18, + 12, + 13, + 9, + 9, + 19, + 16, + 15, + 25, + 56, + 129, + 13, + 19, + 17, + 16, + 12, + 10, + 14, + 25, + 15, + 10, + 22, + 22, + 18, + 11, + 24, + 14, + 16, + 14, + 15, + 21, + 8, + 12, + 13, + 17, + 18, + 21, + 9, + 21, + 56, + 15, + 23, + 13, + 16, + 9, + 11, + 10, + 56, + 9, + 14, + 22, + 16, + 20, + 30, + 13, + 17, + 9, + 11, + 16, + 19, + 16, + 14, + 19, + 7, + 14, + 19, + 18, + 18, + 25, + 20, + 34, + 24, + 8, + 19, + 12, + 27, + 24, + 32, + 15, + 13, + 19, + 24, + 10, + 17, + 16, + 10, + 16, + 9, + 17, + 13, + 14, + 19, + 18, + 14, + 11, + 27, + 23, + 19, + 85, + 69, + 19, + 20, + 14, + 11, + 12, + 8, + 28, + 18, + 16, + 9, + 10, + 14, + 20, + 23, + 19, + 16, + 17, + 15, + 16, + 11, + 16, + 14, + 14, + 16, + 10, + 17, + 13, + 25, + 48, + 23, + 23, + 17, + 15, + 16, + 11, + 11, + 60, + 19, + 16, + 23, + 17, + 11, + 16, + 14, + 19, + 26, + 10, + 19, + 12, + 14, + 11, + 13, + 13, + 14, + 20, + 18, + 14, + 68, + 153, + 193, + 97, + 18, + 13, + 17, + 28, + 19, + 47, + 15, + 22, + 11, + 15, + 17, + 19, + 9, + 13, + 14, + 12, + 15, + 18, + 20, + 17, + 7, + 15, + 17, + 15, + 21, + 15, + 69, + 63, + 11, + 18, + 11, + 20, + 17, + 13, + 40, + 18, + 10, + 10, + 13, + 12, + 19, + 9, + 12, + 18, + 13, + 16, + 11, + 18, + 15, + 22, + 16, + 12, + 22, + 21, + 15, + 45, + 104, + 24, + 10, + 22, + 15, + 15, + 27, + 11, + 19, + 13, + 12, + 9, + 8, + 9, + 6, + 18, + 14, + 15, + 12, + 6, + 18, + 26, + 21, + 26, + 26, + 19, + 14, + 24, + 12, + 18, + 27, + 47, + 14, + 19, + 19, + 25, + 17, + 36, + 43, + 22, + 9, + 9, + 19, + 13, + 15, + 18, + 21, + 9, + 18, + 16, + 8, + 16, + 14, + 8, + 19, + 11, + 22, + 10, + 18, + 58, + 28, + 17, + 14, + 15, + 25, + 8, + 19, + 29, + 27, + 16, + 23, + 19, + 13, + 28, + 21, + 14, + 17, + 17, + 19, + 29, + 14, + 16, + 7, + 17, + 23, + 22, + 16, + 11, + 39, + 88, + 68, + 17, + 6, + 15, + 22, + 27, + 23, + 14, + 17, + 11, + 12, + 17, + 19, + 9, + 12, + 16, + 14, + 22, + 15, + 15, + 14, + 15, + 15, + 21, + 21, + 15, + 17, + 21, + 31, + 20, + 26, + 27, + 17, + 21, + 16, + 15, + 42, + 20, + 15, + 24, + 17, + 23, + 21, + 24, + 15, + 22, + 20, + 15, + 6, + 13, + 26, + 22, + 23, + 18, + 24, + 17, + 20, + 41, + 58, + 24, + 27, + 23, + 20, + 25, + 17, + 17, + 63, + 11, + 9, + 17, + 23, + 11, + 17, + 22, + 11, + 23, + 10, + 15, + 13, + 13, + 20, + 9, + 16, + 22, + 15, + 16, + 28, + 108, + 104, + 57, + 16, + 22, + 22, + 19, + 23, + 27, + 15, + 17, + 20, + 24, + 21, + 19, + 20, + 27, + 26, + 18, + 27, + 19, + 21, + 16, + 18, + 24, + 15, + 28, + 16, + 20, + 29, + 116, + 64, + 19, + 23, + 29, + 20, + 31, + 13, + 49, + 16, + 13, + 26, + 17, + 20, + 16, + 19, + 24, + 28, + 21, + 23, + 25, + 18, + 21, + 18, + 17, + 22, + 19, + 22, + 23, + 4, + 44, + 28, + 12, + 23, + 20, + 22, + 23, + 22, + 37, + 18, + 23, + 25, + 25, + 17, + 12, + 20, + 29, + 26, + 25, + 16, + 23, + 30, + 26, + 20, + 18, + 28, + 25, + 20, + 18, + 70, + 42, + 26, + 22, + 21, + 23, + 26, + 30, + 64, + 47, + 27, + 24, + 18, + 20, + 23, + 18, + 21, + 18, + 15, + 25, + 26, + 22, + 18, + 25, + 23, + 24, + 14, + 16, + 19, + 29, + 75, + 26, + 26, + 23, + 12, + 18, + 21, + 21, + 29, + 18, + 18, + 13, + 22, + 15, + 19, + 12, + 16, + 21, + 23, + 29, + 34, + 15, + 15, + 19, + 22, + 22, + 14, + 24, + 15, + 56, + 32, + 42, + 18, + 12, + 8, + 19, + 20, + 42, + 15, + 19, + 20, + 25, + 22, + 19, + 22, + 19, + 16, + 14, + 14, + 17, + 25, + 14, + 19, + 24, + 18, + 16, + 18, + 13, + 17, + 43, + 33, + 20, + 24, + 24, + 12, + 12, + 10, + 75, + 14, + 20, + 12, + 14, + 14, + 13, + 10, + 12, + 10, + 20, + 23, + 16, + 20, + 16, + 22, + 17, + 17, + 12, + 13, + 12, + 32, + 37, + 19, + 18, + 13, + 12, + 11, + 15, + 16, + 18, + 16, + 16, + 8, + 15, + 13, + 19, + 14, + 12, + 17, + 24, + 12, + 13, + 23, + 19, + 15, + 21, + 12, + 20, + 15, + 28, + 90, + 41, + 28, + 24, + 12, + 16, + 14, + 22, + 17, + 16, + 16, + 16, + 15, + 17, + 19, + 16, + 20, + 26, + 13, + 11, + 23, + 12, + 23, + 18, + 10, + 18, + 15, + 11, + 21, + 19, + 67, + 21, + 17, + 17, + 20, + 15, + 12, + 15, + 40, + 12, + 22, + 18, + 8, + 11, + 17, + 15, + 19, + 12, + 13, + 14, + 21, + 14, + 11, + 12, + 18, + 20, + 12, + 17, + 20, + 36, + 88, + 34, + 16, + 12, + 13, + 14, + 14, + 20, + 22, + 11, + 10, + 10, + 13, + 18, + 20, + 17, + 9, + 15, + 21, + 17, + 17, + 19, + 14, + 14, + 28, + 18, + 18, + 14, + 12, + 34, + 27, + 13, + 17, + 20, + 14, + 16, + 24, + 39, + 16, + 14, + 14, + 16, + 28, + 15, + 24, + 19, + 17, + 14, + 20, + 20, + 13, + 23, + 19, + 16, + 19, + 21, + 14, + 11, + 37, + 35, + 15, + 9, + 23, + 15, + 12, + 14, + 15, + 14, + 19, + 18, + 12, + 10, + 15, + 16, + 19, + 19, + 11, + 20, + 21, + 16, + 16, + 12, + 16, + 13, + 15, + 14, + 17, + 14, + 41, + 40, + 15, + 15, + 14, + 21, + 19, + 18, + 31, + 18, + 17, + 18, + 16, + 17, + 12, + 14, + 11, + 22, + 15, + 18, + 20, + 14, + 18, + 15, + 20, + 19, + 20, + 18, + 20, + 26, + 35, + 50, + 22, + 16, + 19, + 12, + 13, + 15, + 29, + 24, + 18, + 13, + 17, + 11, + 16, + 21, + 29, + 22, + 12, + 16, + 12, + 12, + 13, + 16, + 25, + 16, + 13, + 10, + 10, + 39, + 99, + 26, + 21, + 14, + 19, + 13, + 13, + 37, + 17, + 14, + 14, + 16, + 21, + 13, + 6, + 12, + 13, + 9, + 9, + 14, + 14, + 11, + 15, + 15, + 8, + 19, + 11, + 11, + 17, + 134, + 84, + 46, + 9, + 15, + 20, + 14, + 12, + 13, + 13, + 15, + 19, + 13, + 12, + 17, + 14, + 13, + 15, + 14, + 21, + 21, + 21, + 20, + 17, + 13, + 18, + 17, + 19, + 26, + 52, + 40, + 14, + 27, + 10, + 16, + 21, + 17, + 13, + 14, + 11, + 13, + 14, + 13, + 13, + 10, + 15, + 9, + 15, + 16, + 23, + 15, + 16, + 15, + 22, + 21, + 20, + 13, + 28, + 17, + 25, + 12, + 18, + 22, + 15, + 16, + 13, + 14, + 32, + 6, + 15, + 13, + 15, + 12, + 15, + 19, + 20, + 14, + 18, + 11, + 16, + 16, + 19, + 10, + 17, + 22, + 14, + 10, + 7, + 21, + 28, + 23, + 19, + 9, + 16, + 19, + 11, + 18, + 23, + 8, + 18, + 14, + 9, + 13, + 17, + 18, + 16, + 8, + 26, + 19, + 11, + 8, + 10, + 9, + 7, + 20, + 13, + 11, + 19, + 38, + 31, + 31, + 39, + 40, + 29, + 9, + 10, + 24, + 15, + 9, + 13, + 21, + 13, + 19, + 12, + 11, + 9, + 17, + 15, + 13, + 12, + 16, + 15, + 13, + 16, + 15, + 18, + 15, + 18, + 37, + 27, + 6, + 13, + 8, + 14, + 13, + 17, + 67, + 17, + 16, + 16, + 16, + 19, + 10, + 14, + 10, + 15, + 14, + 10, + 15, + 14, + 14, + 16, + 11, + 9, + 17, + 16, + 13, + 43, + 22, + 19, + 12, + 16, + 15, + 16, + 8, + 13, + 25, + 14, + 14, + 29, + 12, + 20, + 11, + 12, + 9, + 16, + 13, + 10, + 14, + 18, + 30, + 17, + 11, + 18, + 12, + 10, + 16, + 42, + 17, + 32, + 15, + 20, + 13, + 14, + 16, + 42, + 16, + 16, + 21, + 14, + 16, + 11, + 15, + 14, + 18, + 20, + 10, + 15, + 4, + 16, + 13, + 15, + 15, + 12, + 28, + 14, + 62, + 165, + 84, + 12, + 16, + 7, + 18, + 13, + 15, + 29, + 11, + 18, + 18, + 9, + 12, + 11, + 12, + 16, + 11, + 17, + 19, + 14, + 10, + 13, + 14, + 16, + 15, + 11, + 16, + 18, + 24, + 23, + 11, + 14, + 9, + 11, + 13, + 9, + 34, + 14, + 11, + 9, + 11, + 12, + 12, + 15, + 12, + 26, + 9, + 17, + 10, + 16, + 13, + 8, + 11, + 17, + 7, + 6, + 11, + 24, + 69, + 19, + 16, + 10, + 12, + 12, + 9, + 14, + 11, + 13, + 13, + 17, + 7, + 12, + 19, + 16, + 10, + 5, + 16, + 13, + 10, + 8, + 11, + 11, + 14, + 16, + 8, + 14, + 8, + 44, + 51, + 16, + 7, + 14, + 15, + 21, + 10, + 20, + 30, + 8, + 14, + 18, + 15, + 29, + 17, + 11, + 8, + 11, + 11, + 15, + 14, + 14, + 16, + 11, + 18, + 12, + 8, + 11, + 11, + 12, + 15, + 26, + 22, + 14, + 6, + 14, + 6, + 31, + 7, + 15, + 14, + 11, + 6, + 12, + 15, + 16, + 14, + 17, + 21, + 6, + 12, + 14, + 12, + 10, + 10, + 16, + 13, + 13, + 11, + 62, + 13, + 11, + 15, + 12, + 14, + 9, + 13, + 18, + 17, + 13, + 12, + 9, + 10, + 9, + 18, + 10, + 12, + 10, + 15, + 12, + 15, + 14, + 20, + 14, + 9, + 8, + 7, + 14, + 42, + 40, + 25, + 8, + 11, + 14, + 22, + 12, + 19, + 22, + 11, + 17, + 15, + 12, + 4, + 10, + 11, + 8, + 18, + 10, + 12, + 10, + 16, + 11, + 17, + 15, + 7, + 8, + 18, + 9, + 57, + 13, + 25, + 17, + 13, + 12, + 13, + 11, + 20, + 12, + 14, + 10, + 11, + 9, + 12, + 11, + 16, + 14, + 9, + 12, + 23, + 10, + 14, + 6, + 16, + 20, + 8, + 19, + 12, + 31, + 69, + 26, + 15, + 7, + 12, + 20, + 14, + 14, + 16, + 13, + 12, + 14, + 8, + 9, + 8, + 14, + 10, + 11, + 12, + 14, + 4, + 14, + 15, + 11, + 12, + 14, + 11, + 14, + 9, + 47, + 125, + 21, + 14, + 5, + 15, + 14, + 7, + 20, + 16, + 15, + 12, + 10, + 8, + 18, + 13, + 13, + 18, + 8, + 11, + 19, + 10, + 11, + 20, + 12, + 13, + 5, + 21, + 10, + 7, + 20, + 17, + 14, + 11, + 15, + 24, + 10, + 10, + 61, + 12, + 14, + 15, + 15, + 10, + 11, + 17, + 13, + 14, + 14, + 11, + 19, + 12, + 17, + 14, + 14, + 27, + 18, + 11, + 13, + 32, + 50, + 12, + 8, + 14, + 7, + 10, + 16, + 10, + 23, + 23, + 14, + 15, + 18, + 15, + 14, + 16, + 23, + 9, + 6, + 8, + 8, + 14, + 8, + 16, + 10, + 8, + 17, + 10, + 19, + 18, + 14, + 12, + 8, + 10, + 12, + 20, + 9, + 12, + 8, + 26, + 16, + 5, + 16, + 17, + 8, + 15, + 8, + 15, + 11, + 14, + 11, + 16, + 14, + 7, + 12, + 11, + 10, + 11, + 23, + 45, + 61, + 41, + 11, + 14, + 20, + 17, + 13, + 18, + 20, + 13, + 19, + 9, + 18, + 9, + 13, + 16, + 16, + 15, + 14, + 13, + 11, + 9, + 11, + 16, + 13, + 13, + 18, + 9, + 49, + 15, + 12, + 18, + 15, + 9, + 8, + 8, + 19, + 18, + 16, + 16, + 6, + 17, + 14, + 20, + 8, + 15, + 16, + 8, + 17, + 8, + 10, + 16, + 15, + 18, + 24, + 18, + 11, + 18, + 90, + 94, + 126, + 65, + 12, + 11, + 16, + 24, + 38, + 14, + 13, + 17, + 15, + 13, + 16, + 12, + 13, + 11, + 10, + 22, + 12, + 14, + 9, + 9, + 8, + 15, + 17, + 10, + 13, + 54, + 90, + 135, + 115, + 13, + 16, + 13, + 8, + 16, + 15, + 16, + 10, + 12, + 20, + 10, + 9, + 21, + 26, + 22, + 10, + 12, + 12, + 21, + 14, + 21, + 8, + 15, + 17, + 16, + 11, + 37, + 12, + 25, + 10, + 12, + 10, + 12, + 20, + 30, + 16, + 12, + 9, + 10, + 14, + 14, + 7, + 6, + 13, + 10, + 15, + 9, + 12, + 12, + 17, + 17, + 8, + 19, + 7, + 10, + 16, + 28, + 17, + 15, + 8, + 23, + 10, + 13, + 17, + 13, + 9, + 15, + 12, + 15, + 9, + 17, + 15, + 12, + 16, + 10, + 14, + 21, + 13, + 7, + 10, + 10, + 14, + 11, + 17, + 14, + 77, + 109, + 72, + 10, + 18, + 12, + 12, + 12, + 28, + 57, + 26, + 18, + 19, + 35, + 15, + 17, + 17, + 13, + 11, + 21, + 16, + 17, + 16, + 16, + 18, + 7, + 13, + 12, + 10, + 16, + 20, + 17, + 19, + 10, + 17, + 5, + 12, + 26, + 22, + 14, + 12, + 13, + 11, + 11, + 10, + 11, + 11, + 11, + 16, + 11, + 8, + 11, + 19, + 18, + 12, + 10, + 14, + 7, + 11, + 32, + 44, + 34, + 8, + 16, + 17, + 12, + 16, + 17, + 67, + 21, + 20, + 12, + 24, + 7, + 10, + 15, + 11, + 17, + 12, + 12, + 18, + 21, + 12, + 11, + 8, + 13, + 18, + 9, + 8, + 34, + 11, + 17, + 16, + 8, + 8, + 16, + 12, + 21, + 9, + 20, + 10, + 13, + 12, + 15, + 11, + 9, + 15, + 10, + 25, + 10, + 15, + 14, + 18, + 16, + 12, + 11, + 13, + 11, + 20, + 16, + 16, + 23, + 9, + 21, + 12, + 18, + 18, + 15, + 15, + 4, + 15, + 13, + 14, + 12, + 20, + 11, + 18, + 17, + 13, + 9, + 11, + 13, + 24, + 20, + 16, + 15, + 17, + 25, + 72, + 104, + 167, + 175, + 10, + 12, + 18, + 8, + 23, + 17, + 11, + 12, + 14, + 15, + 12, + 10, + 14, + 18, + 13, + 19, + 21, + 24, + 22, + 12, + 15, + 15, + 12, + 16, + 12, + 10, + 34, + 16, + 20, + 17, + 10, + 11, + 15, + 12, + 51, + 28, + 16, + 18, + 7, + 3, + 13, + 14, + 18, + 15, + 8, + 13, + 10, + 12, + 14, + 10, + 13, + 11, + 15, + 13, + 20, + 22, + 44, + 24, + 15, + 11, + 11, + 12, + 21, + 14, + 25, + 17, + 14, + 13, + 18, + 10, + 25, + 22, + 13, + 10, + 19, + 9, + 18, + 11, + 20, + 22, + 20, + 24, + 15, + 15, + 10, + 46, + 43, + 24, + 15, + 14, + 11, + 24, + 14, + 15, + 16, + 12, + 26, + 12, + 14, + 16, + 15, + 20, + 13, + 17, + 17, + 11, + 16, + 11, + 15, + 20, + 22, + 13, + 10, + 14, + 11, + 26, + 10, + 5, + 9, + 6, + 17, + 8, + 21, + 26, + 18, + 11, + 14, + 12, + 13, + 10, + 19, + 9, + 11, + 13, + 17, + 23, + 14, + 12, + 16, + 17, + 13, + 21, + 14, + 28, + 50, + 147, + 42, + 36, + 18, + 11, + 14, + 19, + 25, + 65, + 18, + 12, + 16, + 17, + 12, + 10, + 16, + 6, + 19, + 17, + 20, + 8, + 12, + 12, + 19, + 26, + 13, + 10, + 11, + 12, + 22, + 23, + 9, + 9, + 13, + 13, + 13, + 12, + 31, + 12, + 13, + 14, + 14, + 11, + 13, + 12, + 16, + 5, + 8, + 19, + 17, + 17, + 17, + 12, + 30, + 19, + 10, + 17, + 24, + 40, + 32, + 17, + 12, + 15, + 10, + 15, + 12, + 16, + 31, + 22, + 16, + 16, + 8, + 8, + 17, + 11, + 15, + 10, + 9, + 10, + 17, + 17, + 18, + 14, + 13, + 17, + 16, + 15, + 10, + 92, + 148, + 20, + 13, + 10, + 12, + 12, + 12, + 12, + 26, + 13, + 18, + 20, + 11, + 16, + 9, + 7, + 19, + 20, + 15, + 8, + 12, + 22, + 14, + 27, + 16, + 18, + 10, + 14, + 22, + 24, + 26, + 11, + 10, + 14, + 14, + 21, + 12, + 62, + 62, + 16, + 13, + 14, + 14, + 16, + 12, + 5, + 16, + 12, + 21, + 16, + 19, + 17, + 13, + 12, + 9, + 15, + 18, + 10, + 65, + 32, + 10, + 22, + 11, + 21, + 22, + 17, + 29, + 18, + 13, + 9, + 12, + 17, + 20, + 9, + 13, + 24, + 10, + 16, + 19, + 13, + 19, + 13, + 21, + 14, + 14, + 14, + 13, + 20, + 48, + 31, + 12, + 10, + 13, + 8, + 16, + 16, + 32, + 8, + 5, + 17, + 11, + 11, + 11, + 19, + 11, + 13, + 16, + 18, + 13, + 15, + 11, + 17, + 13, + 19, + 13, + 13, + 4, + 22, + 23, + 20, + 13, + 15, + 15, + 15, + 16, + 11, + 18, + 15, + 9, + 16, + 10, + 16, + 7, + 15, + 10, + 11, + 14, + 16, + 11, + 16, + 12, + 11, + 8, + 16, + 12, + 12, + 13, + 49, + 136, + 92, + 82, + 15, + 12, + 11, + 16, + 15, + 14, + 17, + 11, + 15, + 14, + 17, + 13, + 17, + 17, + 13, + 13, + 15, + 19, + 13, + 15, + 28, + 13, + 10, + 22, + 8, + 17, + 30, + 17, + 8, + 10, + 9, + 15, + 12, + 5, + 23, + 9, + 16, + 16, + 9, + 9, + 9, + 8, + 14, + 11, + 10, + 12, + 9, + 8, + 15, + 14, + 19, + 12, + 14, + 21, + 13, + 43, + 35, + 16, + 11, + 18, + 13, + 17, + 9, + 11, + 11, + 15, + 19, + 16, + 10, + 10, + 15, + 13, + 11, + 12, + 22, + 12, + 22, + 21, + 16, + 9, + 18, + 14, + 27, + 14, + 20, + 82, + 155, + 118, + 15, + 11, + 12, + 7, + 9, + 22, + 13, + 6, + 13, + 19, + 10, + 16, + 19, + 13, + 13, + 9, + 17, + 17, + 17, + 19, + 27, + 20, + 14, + 21, + 10, + 17, + 14, + 59, + 11, + 19, + 20, + 17, + 12, + 18, + 11, + 16, + 15, + 21, + 23, + 16, + 13, + 16, + 11, + 10, + 16, + 11, + 16, + 9, + 13, + 11, + 11, + 14, + 13, + 14, + 11, + 8, + 50, + 24, + 65, + 12, + 12, + 21, + 15, + 10, + 14, + 13, + 20, + 9, + 9, + 15, + 15, + 15, + 14, + 12, + 15, + 23, + 9, + 21, + 18, + 10, + 19, + 13, + 21, + 16, + 14, + 15, + 25, + 10, + 25, + 12, + 20, + 16, + 18, + 18, + 35, + 18, + 15, + 19, + 14, + 6, + 16, + 25, + 13, + 14, + 13, + 13, + 26, + 11, + 17, + 17, + 21, + 10, + 16, + 14, + 10, + 24, + 25, + 21, + 26, + 17, + 18, + 10, + 7, + 16, + 35, + 16, + 12, + 11, + 11, + 17, + 13, + 6, + 16, + 12, + 16, + 17, + 11, + 11, + 11, + 11, + 19, + 12, + 16, + 10, + 11, + 22, + 94, + 59, + 18, + 15, + 15, + 18, + 15, + 18, + 18, + 15, + 7, + 11, + 17, + 13, + 11, + 9, + 11, + 13, + 20, + 11, + 15, + 9, + 16, + 16, + 7, + 18, + 9, + 10, + 14, + 12, + 32, + 9, + 15, + 9, + 16, + 28, + 15, + 32, + 14, + 18, + 18, + 16, + 20, + 8, + 23, + 23, + 19, + 12, + 15, + 16, + 14, + 21, + 19, + 16, + 16, + 13, + 7, + 17, + 50, + 48, + 16, + 16, + 12, + 22, + 19, + 21, + 8, + 22, + 16, + 11, + 12, + 17, + 18, + 13, + 11, + 14, + 27, + 24, + 25, + 16, + 12, + 8, + 11, + 22, + 21, + 15, + 22, + 29, + 75, + 36, + 59, + 18, + 25, + 20, + 16, + 13, + 48, + 24, + 22, + 23, + 18, + 10, + 18, + 18, + 11, + 14, + 18, + 11, + 13, + 15, + 25, + 15, + 15, + 26, + 24, + 21, + 30, + 53, + 47, + 26, + 16, + 20, + 18, + 13, + 14, + 14, + 25, + 12, + 13, + 15, + 16, + 13, + 26, + 16, + 16, + 15, + 19, + 14, + 18, + 15, + 22, + 15, + 23, + 10, + 15, + 8, + 16, + 5, + 58, + 27, + 17, + 18, + 20, + 24, + 24, + 40, + 21, + 25, + 12, + 18, + 22, + 17, + 22, + 24, + 15, + 10, + 15, + 22, + 19, + 22, + 20, + 15, + 22, + 17, + 16, + 23, + 29, + 82, + 37, + 27, + 18, + 17, + 23, + 25, + 12, + 26, + 18, + 25, + 20, + 12, + 12, + 12, + 27, + 13, + 22, + 20, + 21, + 16, + 27, + 23, + 19, + 15, + 12, + 23, + 20, + 8, + 64, + 60, + 33, + 17, + 19, + 23, + 17, + 12, + 23, + 29, + 21, + 17, + 12, + 18, + 17, + 11, + 16, + 24, + 20, + 25, + 21, + 19, + 18, + 15, + 31, + 18, + 24, + 21, + 16, + 26, + 20, + 23, + 43, + 18, + 16, + 11, + 19, + 17, + 38, + 10, + 9, + 25, + 15, + 16, + 17, + 18, + 19, + 18, + 13, + 14, + 19, + 20, + 22, + 8, + 27, + 27, + 12, + 18, + 21, + 26, + 22, + 30, + 21, + 23, + 16, + 21, + 16, + 13, + 20, + 24, + 11, + 16, + 17, + 18, + 19, + 18, + 16, + 26, + 16, + 14, + 17, + 23, + 22, + 16, + 17, + 24, + 18, + 10, + 18, + 43, + 25, + 12, + 19, + 15, + 14, + 15, + 16, + 26, + 26, + 13, + 13, + 21, + 17, + 27, + 21, + 13, + 11, + 16, + 13, + 13, + 17, + 19, + 10, + 18, + 24, + 17, + 19, + 18, + 19, + 102, + 106, + 12, + 13, + 10, + 14, + 12, + 19, + 49, + 25, + 17, + 18, + 12, + 30, + 9, + 18, + 9, + 14, + 19, + 24, + 17, + 16, + 16, + 18, + 17, + 20, + 20, + 22, + 19, + 38, + 38, + 18, + 16, + 14, + 7, + 19, + 9, + 24, + 25, + 13, + 10, + 19, + 27, + 14, + 16, + 12, + 10, + 16, + 15, + 16, + 15, + 11, + 13, + 23, + 20, + 10, + 20, + 15, + 16, + 119, + 64, + 21, + 16, + 18, + 18, + 14, + 14, + 35, + 17, + 16, + 14, + 13, + 15, + 16, + 16, + 14, + 18, + 20, + 23, + 15, + 19, + 17, + 20, + 18, + 22, + 18, + 13, + 19, + 39, + 44, + 22, + 28, + 17, + 19, + 19, + 6, + 13, + 22, + 15, + 13, + 18, + 21, + 25, + 14, + 20, + 21, + 13, + 18, + 20, + 19, + 14, + 18, + 20, + 12, + 14, + 18, + 11, + 18, + 28, + 24, + 22, + 17, + 19, + 9, + 15, + 18, + 37, + 13, + 13, + 11, + 20, + 9, + 8, + 12, + 11, + 8, + 8, + 12, + 24, + 16, + 9, + 15, + 11, + 14, + 13, + 18, + 22, + 31, + 27, + 33, + 15, + 15, + 17, + 10, + 16, + 18, + 21, + 15, + 14, + 16, + 18, + 12, + 14, + 15, + 9, + 19, + 8, + 11, + 18, + 15, + 11, + 20, + 12, + 29, + 13, + 14, + 15, + 32, + 18, + 16, + 16, + 14, + 13, + 7, + 14, + 18, + 13, + 10, + 13, + 18, + 9, + 16, + 19, + 11, + 9, + 15, + 18, + 12, + 11, + 14, + 19, + 13, + 10, + 14, + 20, + 16, + 20, + 62, + 13, + 16, + 12, + 18, + 16, + 18, + 12, + 15, + 11, + 11, + 19, + 15, + 20, + 13, + 9, + 11, + 15, + 13, + 10, + 15, + 11, + 12, + 7, + 15, + 15, + 21, + 21, + 18, + 57, + 131, + 14, + 11, + 15, + 12, + 11, + 13, + 20, + 19, + 6, + 16, + 9, + 14, + 13, + 16, + 22, + 11, + 10, + 12, + 23, + 21, + 22, + 22, + 10, + 9, + 15, + 13, + 15, + 14, + 25, + 30, + 26, + 18, + 19, + 23, + 24, + 15, + 37, + 49, + 16, + 11, + 12, + 9, + 16, + 7, + 15, + 12, + 9, + 11, + 9, + 20, + 10, + 14, + 10, + 15, + 15, + 8, + 11, + 18, + 58, + 12, + 14, + 9, + 12, + 12, + 11, + 13, + 15, + 15, + 18, + 9, + 15, + 24, + 10, + 11, + 9, + 14, + 7, + 20, + 19, + 13, + 16, + 16, + 17, + 15, + 20, + 17, + 15, + 31, + 47, + 23, + 14, + 12, + 21, + 17, + 18, + 12, + 15, + 17, + 24, + 17, + 13, + 16, + 21, + 14, + 16, + 12, + 19, + 14, + 15, + 9, + 10, + 16, + 15, + 14, + 11, + 10, + 14, + 53, + 91, + 82, + 16, + 13, + 11, + 14, + 9, + 22, + 10, + 15, + 13, + 18, + 6, + 10, + 7, + 19, + 18, + 15, + 11, + 9, + 19, + 5, + 11, + 15, + 21, + 24, + 12, + 10, + 31, + 81, + 21, + 12, + 12, + 24, + 12, + 11, + 9, + 10, + 16, + 9, + 12, + 17, + 16, + 15, + 15, + 18, + 7, + 21, + 10, + 18, + 18, + 19, + 10, + 10, + 13, + 15, + 10, + 10, + 63, + 66, + 25, + 13, + 16, + 7, + 15, + 15, + 27, + 14, + 14, + 10, + 15, + 15, + 16, + 9, + 15, + 9, + 19, + 12, + 14, + 9, + 18, + 17, + 17, + 18, + 11, + 18, + 14, + 16, + 36, + 15, + 13, + 13, + 12, + 13, + 12, + 11, + 31, + 14, + 9, + 13, + 19, + 12, + 22, + 12, + 12, + 12, + 15, + 17, + 8, + 17, + 13, + 18, + 7, + 12, + 15, + 14, + 14, + 54, + 51, + 54, + 15, + 9, + 19, + 12, + 13, + 17, + 13, + 14, + 13, + 17, + 18, + 15, + 12, + 9, + 11, + 15, + 17, + 13, + 8, + 11, + 19, + 24, + 22, + 14, + 14, + 13, + 17, + 21, + 12, + 15, + 15, + 10, + 15, + 10, + 11, + 17, + 11, + 19, + 19, + 13, + 16, + 21, + 12, + 8, + 9, + 11, + 9, + 23, + 21, + 21, + 11, + 12, + 13, + 20, + 10, + 17, + 9, + 30, + 35, + 35, + 15, + 22, + 13, + 15, + 26, + 47, + 11, + 14, + 21, + 10, + 10, + 14, + 5, + 15, + 13, + 15, + 12, + 9, + 13, + 17, + 20, + 12, + 14, + 9, + 17, + 11, + 74, + 51, + 10, + 6, + 10, + 19, + 16, + 19, + 19, + 17, + 11, + 17, + 13, + 16, + 18, + 13, + 10, + 10, + 18, + 12, + 10, + 9, + 16, + 17, + 12, + 13, + 16, + 12, + 14, + 16, + 108, + 26, + 22, + 24, + 6, + 10, + 16, + 20, + 33, + 10, + 14, + 13, + 14, + 15, + 13, + 20, + 11, + 8, + 12, + 14, + 8, + 12, + 13, + 13, + 10, + 12, + 10, + 15, + 14, + 33, + 81, + 31, + 15, + 12, + 10, + 12, + 15, + 18, + 68, + 34, + 8, + 13, + 9, + 15, + 13, + 14, + 7, + 12, + 14, + 9, + 9, + 14, + 15, + 20, + 12, + 9, + 11, + 13, + 10, + 53, + 19, + 30, + 12, + 15, + 12, + 25, + 14, + 17, + 14, + 15, + 14, + 14, + 12, + 13, + 13, + 8, + 17, + 20, + 7, + 14, + 25, + 13, + 12, + 16, + 17, + 19, + 9, + 12, + 24, + 29, + 22, + 6, + 12, + 16, + 15, + 10, + 7, + 22, + 15, + 15, + 10, + 10, + 9, + 9, + 20, + 12, + 7, + 11, + 12, + 14, + 22, + 16, + 21, + 20, + 16, + 18, + 21, + 20, + 35, + 116, + 37, + 28, + 19, + 9, + 12, + 15, + 12, + 26, + 20, + 15, + 11, + 12, + 18, + 13, + 19, + 24, + 14, + 22, + 16, + 11, + 25, + 23, + 19, + 8, + 16, + 15, + 21, + 21, + 33, + 16, + 19, + 17, + 12, + 15, + 16, + 14, + 37, + 15, + 17, + 22, + 18, + 13, + 19, + 14, + 14, + 19, + 17, + 19, + 11, + 7, + 20, + 16, + 17, + 16, + 20, + 15, + 16, + 42, + 35, + 20, + 30, + 17, + 17, + 16, + 18, + 18, + 19, + 15, + 16, + 22, + 9, + 16, + 13, + 8, + 14, + 20, + 10, + 20, + 19, + 20, + 13, + 10, + 16, + 9, + 10, + 25, + 17, + 31, + 16, + 20, + 24, + 5, + 13, + 17, + 9, + 34, + 12, + 9, + 19, + 23, + 19, + 10, + 17, + 12, + 15, + 11, + 16, + 16, + 20, + 14, + 18, + 17, + 17, + 13, + 17, + 10, + 27, + 47, + 24, + 23, + 22, + 22, + 23, + 16, + 17, + 17, + 18, + 18, + 15, + 18, + 13, + 20, + 18, + 10, + 8, + 18, + 15, + 14, + 19, + 13, + 14, + 31, + 17, + 8, + 9, + 15, + 65, + 19, + 20, + 17, + 17, + 24, + 13, + 14, + 25, + 7, + 21, + 11, + 18, + 12, + 17, + 8, + 19, + 10, + 11, + 13, + 17, + 8, + 25, + 12, + 23, + 10, + 11, + 17, + 7, + 19, + 70, + 15, + 23, + 7, + 10, + 14, + 13, + 14, + 19, + 11, + 10, + 19, + 16, + 17, + 13, + 15, + 15, + 14, + 15, + 13, + 16, + 19, + 15, + 15, + 11, + 19, + 14, + 15, + 16, + 26, + 18, + 12, + 12, + 10, + 9, + 8, + 15, + 10, + 17, + 16, + 19, + 13, + 15, + 15, + 16, + 19, + 13, + 16, + 15, + 12, + 14, + 9, + 6, + 11, + 12, + 16, + 11, + 13, + 7, + 21, + 31, + 16, + 10, + 8, + 17, + 14, + 12, + 12, + 13, + 14, + 23, + 11, + 13, + 19, + 12, + 21, + 10, + 11, + 20, + 16, + 21, + 19, + 15, + 7, + 20, + 16, + 21, + 16, + 10, + 36, + 15, + 17, + 14, + 14, + 9, + 18, + 11, + 23, + 15, + 16, + 11, + 11, + 10, + 13, + 11, + 14, + 4, + 16, + 16, + 16, + 7, + 20, + 13, + 16, + 15, + 13, + 15, + 18, + 61, + 32, + 48, + 16, + 16, + 8, + 15, + 17, + 15, + 12, + 10, + 14, + 15, + 13, + 15, + 16, + 12, + 12, + 11, + 16, + 16, + 9, + 20, + 16, + 21, + 14, + 18, + 17, + 13, + 12, + 88, + 98, + 18, + 13, + 10, + 14, + 11, + 8, + 25, + 14, + 10, + 16, + 9, + 18, + 18, + 18, + 13, + 29, + 11, + 12, + 10, + 13, + 11, + 7, + 12, + 25, + 16, + 18, + 10, + 15, + 30, + 17, + 14, + 23, + 16, + 19, + 12, + 6, + 44, + 22, + 7, + 10, + 17, + 10, + 15, + 9, + 13, + 15, + 11, + 11, + 9, + 9, + 16, + 16, + 14, + 24, + 14, + 13, + 21, + 27, + 120, + 30, + 11, + 9, + 25, + 16, + 18, + 16, + 13, + 12, + 10, + 15, + 21, + 11, + 13, + 12, + 14, + 12, + 12, + 20, + 14, + 22, + 15, + 20, + 14, + 22, + 13, + 15, + 7, + 40, + 21, + 16, + 23, + 27, + 22, + 10, + 16, + 16, + 19, + 14, + 20, + 13, + 15, + 25, + 15, + 18, + 13, + 17, + 14, + 18, + 16, + 20, + 14, + 16, + 20, + 16, + 13, + 15, + 24, + 46, + 17, + 15, + 17, + 10, + 16, + 19, + 24, + 67, + 14, + 4, + 14, + 19, + 15, + 14, + 16, + 10, + 12, + 12, + 15, + 13, + 16, + 12, + 10, + 16, + 21, + 13, + 18, + 20, + 86, + 28, + 53, + 12, + 17, + 15, + 15, + 10, + 23, + 14, + 15, + 19, + 25, + 12, + 10, + 15, + 11, + 13, + 14, + 20, + 19, + 15, + 16, + 10, + 15, + 17, + 21, + 19, + 15, + 20, + 39, + 20, + 18, + 16, + 13, + 17, + 12, + 26, + 23, + 20, + 10, + 25, + 18, + 14, + 16, + 12, + 18, + 15, + 12, + 14, + 18, + 9, + 27, + 16, + 18, + 14, + 15, + 17, + 12, + 51, + 94, + 24, + 14, + 14, + 21, + 18, + 14, + 26, + 18, + 18, + 23, + 19, + 16, + 15, + 20, + 11, + 16, + 19, + 25, + 14, + 20, + 11, + 5, + 15, + 15, + 20, + 13, + 21, + 11, + 43, + 34, + 21, + 14, + 9, + 11, + 14, + 24, + 16, + 19, + 28, + 25, + 26, + 15, + 16, + 17, + 9, + 24, + 17, + 19, + 15, + 12, + 14, + 23, + 9, + 15, + 19, + 18, + 23, + 38, + 40, + 27, + 13, + 18, + 12, + 16, + 12, + 18, + 44, + 27, + 10, + 14, + 19, + 8, + 14, + 14, + 15, + 18, + 16, + 17, + 12, + 20, + 18, + 15, + 15, + 15, + 16, + 20, + 18, + 34, + 32, + 69, + 17, + 12, + 19, + 20, + 5, + 22, + 14, + 29, + 20, + 14, + 14, + 17, + 17, + 13, + 11, + 17, + 12, + 12, + 11, + 17, + 13, + 9, + 14, + 17, + 15, + 22, + 13, + 26, + 18, + 21, + 16, + 14, + 7, + 9, + 10, + 28, + 16, + 13, + 14, + 16, + 33, + 6, + 15, + 14, + 16, + 16, + 20, + 16, + 18, + 16, + 10, + 13, + 18, + 14, + 18, + 23, + 49, + 147, + 39, + 14, + 11, + 11, + 22, + 15, + 16, + 35, + 21, + 13, + 14, + 12, + 11, + 5, + 14, + 12, + 17, + 10, + 19, + 18, + 13, + 17, + 11, + 14, + 15, + 12, + 16, + 19, + 58, + 64, + 42, + 16, + 19, + 19, + 14, + 12, + 31, + 12, + 17, + 19, + 13, + 16, + 15, + 18, + 14, + 10, + 11, + 21, + 17, + 12, + 18, + 11, + 6, + 14, + 12, + 30, + 16, + 82, + 18, + 19, + 16, + 13, + 15, + 16, + 17, + 12, + 14, + 18, + 19, + 18, + 10, + 19, + 11, + 14, + 11, + 18, + 12, + 18, + 10, + 10, + 18, + 19, + 17, + 20, + 8, + 21, + 21, + 32, + 22, + 14, + 21, + 10, + 17, + 10, + 13, + 17, + 14, + 15, + 7, + 13, + 12, + 17, + 20, + 12, + 8, + 12, + 13, + 14, + 10, + 13, + 19, + 17, + 10, + 10, + 22, + 13, + 25, + 79, + 21, + 12, + 20, + 17, + 18, + 9, + 12, + 26, + 18, + 24, + 19, + 29, + 12, + 16, + 9, + 19, + 13, + 12, + 11, + 11, + 9, + 12, + 18, + 11, + 19, + 7, + 17, + 15, + 43, + 22, + 14, + 20, + 10, + 12, + 16, + 10, + 15, + 9, + 21, + 16, + 16, + 13, + 18, + 20, + 19, + 15, + 15, + 12, + 8, + 20, + 10, + 12, + 12, + 12, + 10, + 18, + 13, + 20, + 85, + 51, + 35, + 12, + 12, + 16, + 12, + 12, + 28, + 14, + 18, + 15, + 9, + 14, + 18, + 13, + 11, + 15, + 22, + 10, + 13, + 24, + 9, + 15, + 18, + 21, + 20, + 14, + 18, + 30, + 24, + 23, + 18, + 20, + 18, + 11, + 13, + 14, + 11, + 15, + 10, + 19, + 10, + 13, + 17, + 15, + 17, + 14, + 11, + 15, + 9, + 18, + 19, + 22, + 21, + 11, + 14, + 13, + 12, + 24, + 19, + 19, + 8, + 18, + 17, + 21, + 9, + 19, + 9, + 10, + 14, + 16, + 20, + 8, + 19, + 10, + 15, + 18, + 14, + 14, + 11, + 12, + 15, + 9, + 11, + 14, + 19, + 13, + 22, + 121, + 107, + 15, + 17, + 12, + 12, + 13, + 11, + 12, + 12, + 16, + 14, + 9, + 18, + 8, + 19, + 20, + 11, + 7, + 8, + 18, + 11, + 17, + 25, + 8, + 17, + 18, + 13, + 14, + 67, + 41, + 18, + 19, + 12, + 9, + 17, + 15, + 15, + 14, + 14, + 17, + 9, + 18, + 16, + 13, + 18, + 13, + 9, + 17, + 19, + 20, + 15, + 25, + 14, + 13, + 16, + 19, + 12, + 14, + 60, + 23, + 23, + 16, + 18, + 12, + 21, + 15, + 43, + 22, + 18, + 25, + 17, + 15, + 10, + 17, + 14, + 13, + 16, + 10, + 13, + 10, + 9, + 19, + 14, + 16, + 15, + 17, + 16, + 68, + 64, + 50, + 16, + 17, + 11, + 15, + 15, + 35, + 12, + 18, + 16, + 9, + 27, + 15, + 13, + 21, + 13, + 20, + 12, + 13, + 11, + 12, + 20, + 17, + 17, + 22, + 12, + 13, + 27, + 107, + 86, + 14, + 14, + 23, + 14, + 20, + 20, + 34, + 22, + 11, + 17, + 13, + 4, + 8, + 13, + 12, + 7, + 8, + 14, + 6, + 9, + 9, + 17, + 13, + 18, + 16, + 14, + 15, + 21, + 16, + 37, + 13, + 12, + 20, + 10, + 12, + 10, + 11, + 15, + 19, + 15, + 7, + 12, + 13, + 12, + 19, + 12, + 6, + 11, + 22, + 15, + 15, + 13, + 13, + 10, + 10, + 10, + 20, + 30, + 13, + 12, + 12, + 11, + 13, + 9, + 13, + 69, + 32, + 13, + 18, + 8, + 17, + 15, + 13, + 13, + 8, + 15, + 11, + 12, + 16, + 14, + 13, + 12, + 12, + 15, + 12, + 17, + 49, + 11, + 10, + 17, + 12, + 18, + 15, + 13, + 20, + 12, + 14, + 16, + 17, + 20, + 21, + 8, + 14, + 14, + 13, + 14, + 9, + 17, + 14, + 20, + 15, + 17, + 13, + 11, + 19, + 10, + 78, + 60, + 15, + 16, + 16, + 13, + 17, + 14, + 25, + 11, + 17, + 25, + 15, + 14, + 17, + 6, + 16, + 20, + 12, + 11, + 16, + 20, + 13, + 10, + 10, + 22, + 18, + 20, + 20, + 35, + 104, + 17, + 15, + 13, + 15, + 16, + 25, + 20, + 18, + 13, + 13, + 18, + 15, + 24, + 14, + 13, + 13, + 13, + 19, + 9, + 18, + 14, + 16, + 18, + 11, + 12, + 17, + 15, + 24, + 73, + 25, + 30, + 19, + 10, + 18, + 14, + 14, + 21, + 18, + 16, + 13, + 15, + 14, + 27, + 9, + 12, + 18, + 14, + 14, + 13, + 16, + 11, + 18, + 10, + 15, + 12, + 11, + 13, + 42, + 75, + 31, + 12, + 10, + 10, + 10, + 10, + 21, + 15, + 21, + 19, + 12, + 15, + 8, + 13, + 14, + 8, + 18, + 10, + 21, + 16, + 11, + 3, + 16, + 11, + 8, + 13, + 12, + 11, + 23, + 17, + 13, + 21, + 16, + 13, + 15, + 20, + 19, + 13, + 18, + 24, + 9, + 15, + 10, + 13, + 13, + 18, + 15, + 14, + 12, + 15, + 14, + 12, + 16, + 19, + 20, + 18, + 18, + 24, + 41, + 19, + 14, + 10, + 11, + 19, + 16, + 9, + 25, + 9, + 7, + 15, + 22, + 15, + 12, + 10, + 18, + 19, + 10, + 11, + 11, + 21, + 10, + 18, + 19, + 11, + 11, + 14, + 11, + 39, + 60, + 14, + 12, + 11, + 17, + 12, + 24, + 17, + 18, + 8, + 14, + 15, + 9, + 12, + 24, + 10, + 10, + 14, + 17, + 15, + 9, + 16, + 15, + 10, + 14, + 19, + 14, + 16, + 20, + 49, + 22, + 19, + 9, + 16, + 13, + 9, + 8, + 19, + 26, + 11, + 15, + 18, + 16, + 11, + 11, + 9, + 12, + 9, + 14, + 16, + 16, + 20, + 11, + 18, + 10, + 11, + 21, + 10, + 19, + 113, + 143, + 124, + 15, + 9, + 18, + 16, + 20, + 15, + 14, + 14, + 15, + 15, + 15, + 18, + 13, + 12, + 11, + 10, + 14, + 13, + 9, + 13, + 20, + 12, + 10, + 10, + 20, + 11, + 31, + 19, + 14, + 9, + 14, + 13, + 21, + 12, + 12, + 27, + 14, + 28, + 14, + 15, + 20, + 20, + 10, + 11, + 13, + 16, + 14, + 11, + 18, + 11, + 18, + 10, + 13, + 18, + 12, + 12, + 70, + 25, + 12, + 10, + 28, + 11, + 19, + 12, + 17, + 12, + 11, + 12, + 15, + 24, + 12, + 7, + 15, + 10, + 13, + 19, + 23, + 16, + 12, + 18, + 15, + 15, + 17, + 14, + 16, + 36, + 76, + 19, + 11, + 13, + 13, + 15, + 14, + 18, + 22, + 13, + 20, + 10, + 12, + 18, + 15, + 8, + 14, + 7, + 12, + 14, + 11, + 19, + 11, + 11, + 8, + 13, + 22, + 13, + 10, + 39, + 21, + 30, + 16, + 21, + 16, + 13, + 19, + 52, + 15, + 14, + 20, + 11, + 15, + 16, + 18, + 17, + 16, + 18, + 13, + 24, + 10, + 22, + 9, + 16, + 14, + 16, + 26, + 19, + 43, + 129, + 152, + 85, + 21, + 23, + 21, + 16, + 21, + 16, + 13, + 16, + 16, + 22, + 11, + 15, + 18, + 22, + 16, + 15, + 11, + 18, + 16, + 7, + 7, + 11, + 9, + 13, + 15, + 19, + 24, + 17, + 28, + 13, + 11, + 14, + 13, + 14, + 19, + 16, + 8, + 21, + 16, + 14, + 27, + 17, + 15, + 10, + 16, + 14, + 12, + 16, + 9, + 8, +} diff --git a/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/weightedhistogram.go b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/weightedhistogram.go new file mode 100644 index 000000000..16eed3719 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/VividCortex/gohistogram/weightedhistogram.go @@ -0,0 +1,190 @@ +// Package gohistogram contains implementations of weighted and exponential histograms. +package gohistogram + +// Copyright (c) 2013 VividCortex, Inc. All rights reserved. +// Please see the LICENSE file for applicable license terms. + +import "fmt" + +// A WeightedHistogram implements Histogram. A WeightedHistogram has bins that have values +// which are exponentially weighted moving averages. This allows you keep inserting large +// amounts of data into the histogram and approximate quantiles with recency factored in. +type WeightedHistogram struct { + bins []bin + maxbins int + total float64 + alpha float64 +} + +// NewWeightedHistogram returns a new WeightedHistogram with a maximum of n bins with a decay factor +// of alpha. +// +// There is no "optimal" bin count, but somewhere between 20 and 80 bins should be +// sufficient. +// +// Alpha should be set to 2 / (N+1), where N represents the average age of the moving window. +// For example, a 60-second window with an average age of 30 seconds would yield an +// alpha of 0.064516129. +func NewWeightedHistogram(n int, alpha float64) *WeightedHistogram { + return &WeightedHistogram{ + bins: make([]bin, 0), + maxbins: n, + total: 0, + alpha: alpha, + } +} + +func ewma(existingVal float64, newVal float64, alpha float64) (result float64) { + result = newVal*(1-alpha) + existingVal*alpha + return +} + +func (h *WeightedHistogram) scaleDown(except int) { + for i := range h.bins { + if i != except { + h.bins[i].count = ewma(h.bins[i].count, 0, h.alpha) + } + } +} + +func (h *WeightedHistogram) Add(n float64) { + defer h.trim() + for i := range h.bins { + if h.bins[i].value == n { + h.bins[i].count++ + + defer h.scaleDown(i) + return + } + + if h.bins[i].value > n { + + newbin := bin{value: n, count: 1} + head := append(make([]bin, 0), h.bins[0:i]...) + + head = append(head, newbin) + tail := h.bins[i:] + h.bins = append(head, tail...) + + defer h.scaleDown(i) + return + } + } + + h.bins = append(h.bins, bin{count: 1, value: n}) +} + +func (h *WeightedHistogram) Quantile(q float64) float64 { + count := q * h.total + for i := range h.bins { + count -= float64(h.bins[i].count) + + if count <= 0 { + return h.bins[i].value + } + } + + return -1 +} + +// CDF returns the value of the cumulative distribution function +// at x +func (h *WeightedHistogram) CDF(x float64) float64 { + count := 0.0 + for i := range h.bins { + if h.bins[i].value <= x { + count += float64(h.bins[i].count) + } + } + + return count / h.total +} + +// Mean returns the sample mean of the distribution +func (h *WeightedHistogram) Mean() float64 { + if h.total == 0 { + return 0 + } + + sum := 0.0 + + for i := range h.bins { + sum += h.bins[i].value * h.bins[i].count + } + + return sum / h.total +} + +// Variance returns the variance of the distribution +func (h *WeightedHistogram) Variance() float64 { + if h.total == 0 { + return 0 + } + + sum := 0.0 + mean := h.Mean() + + for i := range h.bins { + sum += (h.bins[i].count * (h.bins[i].value - mean) * (h.bins[i].value - mean)) + } + + return sum / h.total +} + +func (h *WeightedHistogram) Count() float64 { + return h.total +} + +func (h *WeightedHistogram) trim() { + total := 0.0 + for i := range h.bins { + total += h.bins[i].count + } + h.total = total + for len(h.bins) > h.maxbins { + + // Find closest bins in terms of value + minDelta := 1e99 + minDeltaIndex := 0 + for i := range h.bins { + if i == 0 { + continue + } + + if delta := h.bins[i].value - h.bins[i-1].value; delta < minDelta { + minDelta = delta + minDeltaIndex = i + } + } + + // We need to merge bins minDeltaIndex-1 and minDeltaIndex + totalCount := h.bins[minDeltaIndex-1].count + h.bins[minDeltaIndex].count + mergedbin := bin{ + value: (h.bins[minDeltaIndex-1].value* + h.bins[minDeltaIndex-1].count + + h.bins[minDeltaIndex].value* + h.bins[minDeltaIndex].count) / + totalCount, // weighted average + count: totalCount, // summed heights + } + head := append(make([]bin, 0), h.bins[0:minDeltaIndex-1]...) + tail := append([]bin{mergedbin}, h.bins[minDeltaIndex+1:]...) + h.bins = append(head, tail...) + } +} + +// String returns a string reprentation of the histogram, +// which is useful for printing to a terminal. +func (h *WeightedHistogram) String() (str string) { + str += fmt.Sprintln("Total:", h.total) + + for i := range h.bins { + var bar string + for j := 0; j < int(float64(h.bins[i].count)/float64(h.total)*200); j++ { + bar += "." + } + str += fmt.Sprintln(h.bins[i].value, "\t", bar) + } + + return +} diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/.gitignore b/examples/servers/reading-list/vendor/github.com/beorn7/perks/.gitignore new file mode 100644 index 000000000..1bd9209aa --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/.gitignore @@ -0,0 +1,2 @@ +*.test +*.prof diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/LICENSE b/examples/servers/reading-list/vendor/github.com/beorn7/perks/LICENSE new file mode 100644 index 000000000..339177be6 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/README.md b/examples/servers/reading-list/vendor/github.com/beorn7/perks/README.md new file mode 100644 index 000000000..fc0577770 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/README.md @@ -0,0 +1,31 @@ +# Perks for Go (golang.org) + +Perks contains the Go package quantile that computes approximate quantiles over +an unbounded data stream within low memory and CPU bounds. + +For more information and examples, see: +http://godoc.org/github.com/bmizerany/perks + +A very special thank you and shout out to Graham Cormode (Rutgers University), +Flip Korn (AT&T Labs–Research), S. Muthukrishnan (Rutgers University), and +Divesh Srivastava (AT&T Labs–Research) for their research and publication of +[Effective Computation of Biased Quantiles over Data Streams](http://www.cs.rutgers.edu/~muthu/bquant.pdf) + +Thank you, also: +* Armon Dadgar (@armon) +* Andrew Gerrand (@nf) +* Brad Fitzpatrick (@bradfitz) +* Keith Rarick (@kr) + +FAQ: + +Q: Why not move the quantile package into the project root? +A: I want to add more packages to perks later. + +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/bench_test.go b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/bench_test.go new file mode 100644 index 000000000..0bd0e4e77 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/bench_test.go @@ -0,0 +1,63 @@ +package quantile + +import ( + "testing" +) + +func BenchmarkInsertTargeted(b *testing.B) { + b.ReportAllocs() + + s := NewTargeted(Targets) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertTargetedSmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiased(b *testing.B) { + s := NewLowBiased(0.01) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkInsertBiasedSmallEpsilon(b *testing.B) { + s := NewLowBiased(0.0001) + b.ResetTimer() + for i := float64(0); i < float64(b.N); i++ { + s.Insert(i) + } +} + +func BenchmarkQuery(b *testing.B) { + s := NewTargeted(Targets) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} + +func BenchmarkQuerySmallEpsilon(b *testing.B) { + s := NewTargeted(TargetsSmallEpsilon) + for i := float64(0); i < 1e6; i++ { + s.Insert(i) + } + b.ResetTimer() + n := float64(b.N) + for i := float64(0); i < n; i++ { + s.Query(i / n) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/example_test.go b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/example_test.go new file mode 100644 index 000000000..ab3293aaf --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/example_test.go @@ -0,0 +1,121 @@ +// +build go1.1 + +package quantile_test + +import ( + "bufio" + "fmt" + "log" + "os" + "strconv" + "time" + + "github.com/beorn7/perks/quantile" +) + +func Example_simple() { + ch := make(chan float64) + go sendFloats(ch) + + // Compute the 50th, 90th, and 99th percentile. + q := quantile.NewTargeted(map[float64]float64{ + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + }) + for v := range ch { + q.Insert(v) + } + + fmt.Println("perc50:", q.Query(0.50)) + fmt.Println("perc90:", q.Query(0.90)) + fmt.Println("perc99:", q.Query(0.99)) + fmt.Println("count:", q.Count()) + // Output: + // perc50: 5 + // perc90: 16 + // perc99: 223 + // count: 2388 +} + +func Example_mergeMultipleStreams() { + // Scenario: + // We have multiple database shards. On each shard, there is a process + // collecting query response times from the database logs and inserting + // them into a Stream (created via NewTargeted(0.90)), much like the + // Simple example. These processes expose a network interface for us to + // ask them to serialize and send us the results of their + // Stream.Samples so we may Merge and Query them. + // + // NOTES: + // * These sample sets are small, allowing us to get them + // across the network much faster than sending the entire list of data + // points. + // + // * For this to work correctly, we must supply the same quantiles + // a priori the process collecting the samples supplied to NewTargeted, + // even if we do not plan to query them all here. + ch := make(chan quantile.Samples) + getDBQuerySamples(ch) + q := quantile.NewTargeted(map[float64]float64{0.90: 0.001}) + for samples := range ch { + q.Merge(samples) + } + fmt.Println("perc90:", q.Query(0.90)) +} + +func Example_window() { + // Scenario: We want the 90th, 95th, and 99th percentiles for each + // minute. + + ch := make(chan float64) + go sendStreamValues(ch) + + tick := time.NewTicker(1 * time.Minute) + q := quantile.NewTargeted(map[float64]float64{ + 0.90: 0.001, + 0.95: 0.0005, + 0.99: 0.0001, + }) + for { + select { + case t := <-tick.C: + flushToDB(t, q.Samples()) + q.Reset() + case v := <-ch: + q.Insert(v) + } + } +} + +func sendStreamValues(ch chan float64) { + // Use your imagination +} + +func flushToDB(t time.Time, samples quantile.Samples) { + // Use your imagination +} + +// This is a stub for the above example. In reality this would hit the remote +// servers via http or something like it. +func getDBQuerySamples(ch chan quantile.Samples) {} + +func sendFloats(ch chan<- float64) { + f, err := os.Open("exampledata.txt") + if err != nil { + log.Fatal(err) + } + sc := bufio.NewScanner(f) + for sc.Scan() { + b := sc.Bytes() + v, err := strconv.ParseFloat(string(b), 64) + if err != nil { + log.Fatal(err) + } + ch <- v + } + if sc.Err() != nil { + log.Fatal(sc.Err()) + } + close(ch) +} diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/exampledata.txt b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/exampledata.txt new file mode 100644 index 000000000..1602287d7 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/exampledata.txt @@ -0,0 +1,2388 @@ +8 +5 +26 +12 +5 +235 +13 +6 +28 +30 +3 +3 +3 +3 +5 +2 +33 +7 +2 +4 +7 +12 +14 +5 +8 +3 +10 +4 +5 +3 +6 +6 +209 +20 +3 +10 +14 +3 +4 +6 +8 +5 +11 +7 +3 +2 +3 +3 +212 +5 +222 +4 +10 +10 +5 +6 +3 +8 +3 +10 +254 +220 +2 +3 +5 +24 +5 +4 +222 +7 +3 +3 +223 +8 +15 +12 +14 +14 +3 +2 +2 +3 +13 +3 +11 +4 +4 +6 +5 +7 +13 +5 +3 +5 +2 +5 +3 +5 +2 +7 +15 +17 +14 +3 +6 +6 +3 +17 +5 +4 +7 +6 +4 +4 +8 +6 +8 +3 +9 +3 +6 +3 +4 +5 +3 +3 +660 +4 +6 +10 +3 +6 +3 +2 +5 +13 +2 +4 +4 +10 +4 +8 +4 +3 +7 +9 +9 +3 +10 +37 +3 +13 +4 +12 +3 +6 +10 +8 +5 +21 +2 +3 +8 +3 +2 +3 +3 +4 +12 +2 +4 +8 +8 +4 +3 +2 +20 +1 +6 +32 +2 +11 +6 +18 +3 +8 +11 +3 +212 +3 +4 +2 +6 +7 +12 +11 +3 +2 +16 +10 +6 +4 +6 +3 +2 +7 +3 +2 +2 +2 +2 +5 +6 +4 +3 +10 +3 +4 +6 +5 +3 +4 +4 +5 +6 +4 +3 +4 +4 +5 +7 +5 +5 +3 +2 +7 +2 +4 +12 +4 +5 +6 +2 +4 +4 +8 +4 +15 +13 +7 +16 +5 +3 +23 +5 +5 +7 +3 +2 +9 +8 +7 +5 +8 +11 +4 +10 +76 +4 +47 +4 +3 +2 +7 +4 +2 +3 +37 +10 +4 +2 +20 +5 +4 +4 +10 +10 +4 +3 +7 +23 +240 +7 +13 +5 +5 +3 +3 +2 +5 +4 +2 +8 +7 +19 +2 +23 +8 +7 +2 +5 +3 +8 +3 +8 +13 +5 +5 +5 +2 +3 +23 +4 +9 +8 +4 +3 +3 +5 +220 +2 +3 +4 +6 +14 +3 +53 +6 +2 +5 +18 +6 +3 +219 +6 +5 +2 +5 +3 +6 +5 +15 +4 +3 +17 +3 +2 +4 +7 +2 +3 +3 +4 +4 +3 +2 +664 +6 +3 +23 +5 +5 +16 +5 +8 +2 +4 +2 +24 +12 +3 +2 +3 +5 +8 +3 +5 +4 +3 +14 +3 +5 +8 +2 +3 +7 +9 +4 +2 +3 +6 +8 +4 +3 +4 +6 +5 +3 +3 +6 +3 +19 +4 +4 +6 +3 +6 +3 +5 +22 +5 +4 +4 +3 +8 +11 +4 +9 +7 +6 +13 +4 +4 +4 +6 +17 +9 +3 +3 +3 +4 +3 +221 +5 +11 +3 +4 +2 +12 +6 +3 +5 +7 +5 +7 +4 +9 +7 +14 +37 +19 +217 +16 +3 +5 +2 +2 +7 +19 +7 +6 +7 +4 +24 +5 +11 +4 +7 +7 +9 +13 +3 +4 +3 +6 +28 +4 +4 +5 +5 +2 +5 +6 +4 +4 +6 +10 +5 +4 +3 +2 +3 +3 +6 +5 +5 +4 +3 +2 +3 +7 +4 +6 +18 +16 +8 +16 +4 +5 +8 +6 +9 +13 +1545 +6 +215 +6 +5 +6 +3 +45 +31 +5 +2 +2 +4 +3 +3 +2 +5 +4 +3 +5 +7 +7 +4 +5 +8 +5 +4 +749 +2 +31 +9 +11 +2 +11 +5 +4 +4 +7 +9 +11 +4 +5 +4 +7 +3 +4 +6 +2 +15 +3 +4 +3 +4 +3 +5 +2 +13 +5 +5 +3 +3 +23 +4 +4 +5 +7 +4 +13 +2 +4 +3 +4 +2 +6 +2 +7 +3 +5 +5 +3 +29 +5 +4 +4 +3 +10 +2 +3 +79 +16 +6 +6 +7 +7 +3 +5 +5 +7 +4 +3 +7 +9 +5 +6 +5 +9 +6 +3 +6 +4 +17 +2 +10 +9 +3 +6 +2 +3 +21 +22 +5 +11 +4 +2 +17 +2 +224 +2 +14 +3 +4 +4 +2 +4 +4 +4 +4 +5 +3 +4 +4 +10 +2 +6 +3 +3 +5 +7 +2 +7 +5 +6 +3 +218 +2 +2 +5 +2 +6 +3 +5 +222 +14 +6 +33 +3 +2 +5 +3 +3 +3 +9 +5 +3 +3 +2 +7 +4 +3 +4 +3 +5 +6 +5 +26 +4 +13 +9 +7 +3 +221 +3 +3 +4 +4 +4 +4 +2 +18 +5 +3 +7 +9 +6 +8 +3 +10 +3 +11 +9 +5 +4 +17 +5 +5 +6 +6 +3 +2 +4 +12 +17 +6 +7 +218 +4 +2 +4 +10 +3 +5 +15 +3 +9 +4 +3 +3 +6 +29 +3 +3 +4 +5 +5 +3 +8 +5 +6 +6 +7 +5 +3 +5 +3 +29 +2 +31 +5 +15 +24 +16 +5 +207 +4 +3 +3 +2 +15 +4 +4 +13 +5 +5 +4 +6 +10 +2 +7 +8 +4 +6 +20 +5 +3 +4 +3 +12 +12 +5 +17 +7 +3 +3 +3 +6 +10 +3 +5 +25 +80 +4 +9 +3 +2 +11 +3 +3 +2 +3 +8 +7 +5 +5 +19 +5 +3 +3 +12 +11 +2 +6 +5 +5 +5 +3 +3 +3 +4 +209 +14 +3 +2 +5 +19 +4 +4 +3 +4 +14 +5 +6 +4 +13 +9 +7 +4 +7 +10 +2 +9 +5 +7 +2 +8 +4 +6 +5 +5 +222 +8 +7 +12 +5 +216 +3 +4 +4 +6 +3 +14 +8 +7 +13 +4 +3 +3 +3 +3 +17 +5 +4 +3 +33 +6 +6 +33 +7 +5 +3 +8 +7 +5 +2 +9 +4 +2 +233 +24 +7 +4 +8 +10 +3 +4 +15 +2 +16 +3 +3 +13 +12 +7 +5 +4 +207 +4 +2 +4 +27 +15 +2 +5 +2 +25 +6 +5 +5 +6 +13 +6 +18 +6 +4 +12 +225 +10 +7 +5 +2 +2 +11 +4 +14 +21 +8 +10 +3 +5 +4 +232 +2 +5 +5 +3 +7 +17 +11 +6 +6 +23 +4 +6 +3 +5 +4 +2 +17 +3 +6 +5 +8 +3 +2 +2 +14 +9 +4 +4 +2 +5 +5 +3 +7 +6 +12 +6 +10 +3 +6 +2 +2 +19 +5 +4 +4 +9 +2 +4 +13 +3 +5 +6 +3 +6 +5 +4 +9 +6 +3 +5 +7 +3 +6 +6 +4 +3 +10 +6 +3 +221 +3 +5 +3 +6 +4 +8 +5 +3 +6 +4 +4 +2 +54 +5 +6 +11 +3 +3 +4 +4 +4 +3 +7 +3 +11 +11 +7 +10 +6 +13 +223 +213 +15 +231 +7 +3 +7 +228 +2 +3 +4 +4 +5 +6 +7 +4 +13 +3 +4 +5 +3 +6 +4 +6 +7 +2 +4 +3 +4 +3 +3 +6 +3 +7 +3 +5 +18 +5 +6 +8 +10 +3 +3 +3 +2 +4 +2 +4 +4 +5 +6 +6 +4 +10 +13 +3 +12 +5 +12 +16 +8 +4 +19 +11 +2 +4 +5 +6 +8 +5 +6 +4 +18 +10 +4 +2 +216 +6 +6 +6 +2 +4 +12 +8 +3 +11 +5 +6 +14 +5 +3 +13 +4 +5 +4 +5 +3 +28 +6 +3 +7 +219 +3 +9 +7 +3 +10 +6 +3 +4 +19 +5 +7 +11 +6 +15 +19 +4 +13 +11 +3 +7 +5 +10 +2 +8 +11 +2 +6 +4 +6 +24 +6 +3 +3 +3 +3 +6 +18 +4 +11 +4 +2 +5 +10 +8 +3 +9 +5 +3 +4 +5 +6 +2 +5 +7 +4 +4 +14 +6 +4 +4 +5 +5 +7 +2 +4 +3 +7 +3 +3 +6 +4 +5 +4 +4 +4 +3 +3 +3 +3 +8 +14 +2 +3 +5 +3 +2 +4 +5 +3 +7 +3 +3 +18 +3 +4 +4 +5 +7 +3 +3 +3 +13 +5 +4 +8 +211 +5 +5 +3 +5 +2 +5 +4 +2 +655 +6 +3 +5 +11 +2 +5 +3 +12 +9 +15 +11 +5 +12 +217 +2 +6 +17 +3 +3 +207 +5 +5 +4 +5 +9 +3 +2 +8 +5 +4 +3 +2 +5 +12 +4 +14 +5 +4 +2 +13 +5 +8 +4 +225 +4 +3 +4 +5 +4 +3 +3 +6 +23 +9 +2 +6 +7 +233 +4 +4 +6 +18 +3 +4 +6 +3 +4 +4 +2 +3 +7 +4 +13 +227 +4 +3 +5 +4 +2 +12 +9 +17 +3 +7 +14 +6 +4 +5 +21 +4 +8 +9 +2 +9 +25 +16 +3 +6 +4 +7 +8 +5 +2 +3 +5 +4 +3 +3 +5 +3 +3 +3 +2 +3 +19 +2 +4 +3 +4 +2 +3 +4 +4 +2 +4 +3 +3 +3 +2 +6 +3 +17 +5 +6 +4 +3 +13 +5 +3 +3 +3 +4 +9 +4 +2 +14 +12 +4 +5 +24 +4 +3 +37 +12 +11 +21 +3 +4 +3 +13 +4 +2 +3 +15 +4 +11 +4 +4 +3 +8 +3 +4 +4 +12 +8 +5 +3 +3 +4 +2 +220 +3 +5 +223 +3 +3 +3 +10 +3 +15 +4 +241 +9 +7 +3 +6 +6 +23 +4 +13 +7 +3 +4 +7 +4 +9 +3 +3 +4 +10 +5 +5 +1 +5 +24 +2 +4 +5 +5 +6 +14 +3 +8 +2 +3 +5 +13 +13 +3 +5 +2 +3 +15 +3 +4 +2 +10 +4 +4 +4 +5 +5 +3 +5 +3 +4 +7 +4 +27 +3 +6 +4 +15 +3 +5 +6 +6 +5 +4 +8 +3 +9 +2 +6 +3 +4 +3 +7 +4 +18 +3 +11 +3 +3 +8 +9 +7 +24 +3 +219 +7 +10 +4 +5 +9 +12 +2 +5 +4 +4 +4 +3 +3 +19 +5 +8 +16 +8 +6 +22 +3 +23 +3 +242 +9 +4 +3 +3 +5 +7 +3 +3 +5 +8 +3 +7 +5 +14 +8 +10 +3 +4 +3 +7 +4 +6 +7 +4 +10 +4 +3 +11 +3 +7 +10 +3 +13 +6 +8 +12 +10 +5 +7 +9 +3 +4 +7 +7 +10 +8 +30 +9 +19 +4 +3 +19 +15 +4 +13 +3 +215 +223 +4 +7 +4 +8 +17 +16 +3 +7 +6 +5 +5 +4 +12 +3 +7 +4 +4 +13 +4 +5 +2 +5 +6 +5 +6 +6 +7 +10 +18 +23 +9 +3 +3 +6 +5 +2 +4 +2 +7 +3 +3 +2 +5 +5 +14 +10 +224 +6 +3 +4 +3 +7 +5 +9 +3 +6 +4 +2 +5 +11 +4 +3 +3 +2 +8 +4 +7 +4 +10 +7 +3 +3 +18 +18 +17 +3 +3 +3 +4 +5 +3 +3 +4 +12 +7 +3 +11 +13 +5 +4 +7 +13 +5 +4 +11 +3 +12 +3 +6 +4 +4 +21 +4 +6 +9 +5 +3 +10 +8 +4 +6 +4 +4 +6 +5 +4 +8 +6 +4 +6 +4 +4 +5 +9 +6 +3 +4 +2 +9 +3 +18 +2 +4 +3 +13 +3 +6 +6 +8 +7 +9 +3 +2 +16 +3 +4 +6 +3 +2 +33 +22 +14 +4 +9 +12 +4 +5 +6 +3 +23 +9 +4 +3 +5 +5 +3 +4 +5 +3 +5 +3 +10 +4 +5 +5 +8 +4 +4 +6 +8 +5 +4 +3 +4 +6 +3 +3 +3 +5 +9 +12 +6 +5 +9 +3 +5 +3 +2 +2 +2 +18 +3 +2 +21 +2 +5 +4 +6 +4 +5 +10 +3 +9 +3 +2 +10 +7 +3 +6 +6 +4 +4 +8 +12 +7 +3 +7 +3 +3 +9 +3 +4 +5 +4 +4 +5 +5 +10 +15 +4 +4 +14 +6 +227 +3 +14 +5 +216 +22 +5 +4 +2 +2 +6 +3 +4 +2 +9 +9 +4 +3 +28 +13 +11 +4 +5 +3 +3 +2 +3 +3 +5 +3 +4 +3 +5 +23 +26 +3 +4 +5 +6 +4 +6 +3 +5 +5 +3 +4 +3 +2 +2 +2 +7 +14 +3 +6 +7 +17 +2 +2 +15 +14 +16 +4 +6 +7 +13 +6 +4 +5 +6 +16 +3 +3 +28 +3 +6 +15 +3 +9 +2 +4 +6 +3 +3 +22 +4 +12 +6 +7 +2 +5 +4 +10 +3 +16 +6 +9 +2 +5 +12 +7 +5 +5 +5 +5 +2 +11 +9 +17 +4 +3 +11 +7 +3 +5 +15 +4 +3 +4 +211 +8 +7 +5 +4 +7 +6 +7 +6 +3 +6 +5 +6 +5 +3 +4 +4 +26 +4 +6 +10 +4 +4 +3 +2 +3 +3 +4 +5 +9 +3 +9 +4 +4 +5 +5 +8 +2 +4 +2 +3 +8 +4 +11 +19 +5 +8 +6 +3 +5 +6 +12 +3 +2 +4 +16 +12 +3 +4 +4 +8 +6 +5 +6 +6 +219 +8 +222 +6 +16 +3 +13 +19 +5 +4 +3 +11 +6 +10 +4 +7 +7 +12 +5 +3 +3 +5 +6 +10 +3 +8 +2 +5 +4 +7 +2 +4 +4 +2 +12 +9 +6 +4 +2 +40 +2 +4 +10 +4 +223 +4 +2 +20 +6 +7 +24 +5 +4 +5 +2 +20 +16 +6 +5 +13 +2 +3 +3 +19 +3 +2 +4 +5 +6 +7 +11 +12 +5 +6 +7 +7 +3 +5 +3 +5 +3 +14 +3 +4 +4 +2 +11 +1 +7 +3 +9 +6 +11 +12 +5 +8 +6 +221 +4 +2 +12 +4 +3 +15 +4 +5 +226 +7 +218 +7 +5 +4 +5 +18 +4 +5 +9 +4 +4 +2 +9 +18 +18 +9 +5 +6 +6 +3 +3 +7 +3 +5 +4 +4 +4 +12 +3 +6 +31 +5 +4 +7 +3 +6 +5 +6 +5 +11 +2 +2 +11 +11 +6 +7 +5 +8 +7 +10 +5 +23 +7 +4 +3 +5 +34 +2 +5 +23 +7 +3 +6 +8 +4 +4 +4 +2 +5 +3 +8 +5 +4 +8 +25 +2 +3 +17 +8 +3 +4 +8 +7 +3 +15 +6 +5 +7 +21 +9 +5 +6 +6 +5 +3 +2 +3 +10 +3 +6 +3 +14 +7 +4 +4 +8 +7 +8 +2 +6 +12 +4 +213 +6 +5 +21 +8 +2 +5 +23 +3 +11 +2 +3 +6 +25 +2 +3 +6 +7 +6 +6 +4 +4 +6 +3 +17 +9 +7 +6 +4 +3 +10 +7 +2 +3 +3 +3 +11 +8 +3 +7 +6 +4 +14 +36 +3 +4 +3 +3 +22 +13 +21 +4 +2 +7 +4 +4 +17 +15 +3 +7 +11 +2 +4 +7 +6 +209 +6 +3 +2 +2 +24 +4 +9 +4 +3 +3 +3 +29 +2 +2 +4 +3 +3 +5 +4 +6 +3 +3 +2 +4 diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream.go b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream.go new file mode 100644 index 000000000..f4cabd669 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream.go @@ -0,0 +1,292 @@ +// Package quantile computes approximate quantiles over an unbounded data +// stream within low memory and CPU bounds. +// +// A small amount of accuracy is traded to achieve the above properties. +// +// Multiple streams can be merged before calling Query to generate a single set +// of results. This is meaningful when the streams represent the same type of +// data. See Merge and Samples. +// +// For more detailed information about the algorithm used, see: +// +// Effective Computation of Biased Quantiles over Data Streams +// +// http://www.cs.rutgers.edu/~muthu/bquant.pdf +package quantile + +import ( + "math" + "sort" +) + +// Sample holds an observed value and meta information for compression. JSON +// tags have been added for convenience. +type Sample struct { + Value float64 `json:",string"` + Width float64 `json:",string"` + Delta float64 `json:",string"` +} + +// Samples represents a slice of samples. It implements sort.Interface. +type Samples []Sample + +func (a Samples) Len() int { return len(a) } +func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value } +func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type invariant func(s *stream, r float64) float64 + +// NewLowBiased returns an initialized Stream for low-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the lower ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within (1±Epsilon)*Quantile. +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewLowBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * r + } + return newStream(ƒ) +} + +// NewHighBiased returns an initialized Stream for high-biased quantiles +// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but +// error guarantees can still be given even for the higher ranks of the data +// distribution. +// +// The provided epsilon is a relative error, i.e. the true quantile of a value +// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error +// properties. +func NewHighBiased(epsilon float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + return 2 * epsilon * (s.n - r) + } + return newStream(ƒ) +} + +// NewTargeted returns an initialized Stream concerned with a particular set of +// quantile values that are supplied a priori. Knowing these a priori reduces +// space and computation time. The targets map maps the desired quantiles to +// their absolute errors, i.e. the true quantile of a value returned by a query +// is guaranteed to be within (Quantile±Epsilon). +// +// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties. +func NewTargeted(targets map[float64]float64) *Stream { + ƒ := func(s *stream, r float64) float64 { + var m = math.MaxFloat64 + var f float64 + for quantile, epsilon := range targets { + if quantile*s.n <= r { + f = (2 * epsilon * r) / quantile + } else { + f = (2 * epsilon * (s.n - r)) / (1 - quantile) + } + if f < m { + m = f + } + } + return m + } + return newStream(ƒ) +} + +// Stream computes quantiles for a stream of float64s. It is not thread-safe by +// design. Take care when using across multiple goroutines. +type Stream struct { + *stream + b Samples + sorted bool +} + +func newStream(ƒ invariant) *Stream { + x := &stream{ƒ: ƒ} + return &Stream{x, make(Samples, 0, 500), true} +} + +// Insert inserts v into the stream. +func (s *Stream) Insert(v float64) { + s.insert(Sample{Value: v, Width: 1}) +} + +func (s *Stream) insert(sample Sample) { + s.b = append(s.b, sample) + s.sorted = false + if len(s.b) == cap(s.b) { + s.flush() + } +} + +// Query returns the computed qth percentiles value. If s was created with +// NewTargeted, and q is not in the set of quantiles provided a priori, Query +// will return an unspecified result. +func (s *Stream) Query(q float64) float64 { + if !s.flushed() { + // Fast path when there hasn't been enough data for a flush; + // this also yields better accuracy for small sets of data. + l := len(s.b) + if l == 0 { + return 0 + } + i := int(math.Ceil(float64(l) * q)) + if i > 0 { + i -= 1 + } + s.maybeSort() + return s.b[i].Value + } + s.flush() + return s.stream.query(q) +} + +// Merge merges samples into the underlying streams samples. This is handy when +// merging multiple streams from separate threads, database shards, etc. +// +// ATTENTION: This method is broken and does not yield correct results. The +// underlying algorithm is not capable of merging streams correctly. +func (s *Stream) Merge(samples Samples) { + sort.Sort(samples) + s.stream.merge(samples) +} + +// Reset reinitializes and clears the list reusing the samples buffer memory. +func (s *Stream) Reset() { + s.stream.reset() + s.b = s.b[:0] +} + +// Samples returns stream samples held by s. +func (s *Stream) Samples() Samples { + if !s.flushed() { + return s.b + } + s.flush() + return s.stream.samples() +} + +// Count returns the total number of samples observed in the stream +// since initialization. +func (s *Stream) Count() int { + return len(s.b) + s.stream.count() +} + +func (s *Stream) flush() { + s.maybeSort() + s.stream.merge(s.b) + s.b = s.b[:0] +} + +func (s *Stream) maybeSort() { + if !s.sorted { + s.sorted = true + sort.Sort(s.b) + } +} + +func (s *Stream) flushed() bool { + return len(s.stream.l) > 0 +} + +type stream struct { + n float64 + l []Sample + ƒ invariant +} + +func (s *stream) reset() { + s.l = s.l[:0] + s.n = 0 +} + +func (s *stream) insert(v float64) { + s.merge(Samples{{v, 1, 0}}) +} + +func (s *stream) merge(samples Samples) { + // TODO(beorn7): This tries to merge not only individual samples, but + // whole summaries. The paper doesn't mention merging summaries at + // all. Unittests show that the merging is inaccurate. Find out how to + // do merges properly. + var r float64 + i := 0 + for _, sample := range samples { + for ; i < len(s.l); i++ { + c := s.l[i] + if c.Value > sample.Value { + // Insert at position i. + s.l = append(s.l, Sample{}) + copy(s.l[i+1:], s.l[i:]) + s.l[i] = Sample{ + sample.Value, + sample.Width, + math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1), + // TODO(beorn7): How to calculate delta correctly? + } + i++ + goto inserted + } + r += c.Width + } + s.l = append(s.l, Sample{sample.Value, sample.Width, 0}) + i++ + inserted: + s.n += sample.Width + r += sample.Width + } + s.compress() +} + +func (s *stream) count() int { + return int(s.n) +} + +func (s *stream) query(q float64) float64 { + t := math.Ceil(q * s.n) + t += math.Ceil(s.ƒ(s, t) / 2) + p := s.l[0] + var r float64 + for _, c := range s.l[1:] { + r += p.Width + if r+c.Width+c.Delta > t { + return p.Value + } + p = c + } + return p.Value +} + +func (s *stream) compress() { + if len(s.l) < 2 { + return + } + x := s.l[len(s.l)-1] + xi := len(s.l) - 1 + r := s.n - 1 - x.Width + + for i := len(s.l) - 2; i >= 0; i-- { + c := s.l[i] + if c.Width+x.Width+x.Delta <= s.ƒ(s, r) { + x.Width += c.Width + s.l[xi] = x + // Remove element at i. + copy(s.l[i:], s.l[i+1:]) + s.l = s.l[:len(s.l)-1] + xi -= 1 + } else { + x = c + xi = i + } + r -= c.Width + } +} + +func (s *stream) samples() Samples { + samples := make(Samples, len(s.l)) + copy(samples, s.l) + return samples +} diff --git a/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream_test.go b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream_test.go new file mode 100644 index 000000000..855195097 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/beorn7/perks/quantile/stream_test.go @@ -0,0 +1,215 @@ +package quantile + +import ( + "math" + "math/rand" + "sort" + "testing" +) + +var ( + Targets = map[float64]float64{ + 0.01: 0.001, + 0.10: 0.01, + 0.50: 0.05, + 0.90: 0.01, + 0.99: 0.001, + } + TargetsSmallEpsilon = map[float64]float64{ + 0.01: 0.0001, + 0.10: 0.001, + 0.50: 0.005, + 0.90: 0.001, + 0.99: 0.0001, + } + LowQuantiles = []float64{0.01, 0.1, 0.5} + HighQuantiles = []float64{0.99, 0.9, 0.5} +) + +const RelativeEpsilon = 0.01 + +func verifyPercsWithAbsoluteEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for quantile, epsilon := range Targets { + n := float64(len(a)) + k := int(quantile * n) + if k < 1 { + k = 1 + } + lower := int((quantile - epsilon) * n) + if lower < 1 { + lower = 1 + } + upper := int(math.Ceil((quantile + epsilon) * n)) + if upper > len(a) { + upper = len(a) + } + w, min, max := a[k-1], a[lower-1], a[upper-1] + if g := s.Query(quantile); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", quantile, w, min, max, g) + } + } +} + +func verifyLowPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range LowQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - RelativeEpsilon) * qu * n) + upperRank := int(math.Ceil((1 + RelativeEpsilon) * qu * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func verifyHighPercsWithRelativeEpsilon(t *testing.T, a []float64, s *Stream) { + sort.Float64s(a) + for _, qu := range HighQuantiles { + n := float64(len(a)) + k := int(qu * n) + + lowerRank := int((1 - (1+RelativeEpsilon)*(1-qu)) * n) + upperRank := int(math.Ceil((1 - (1-RelativeEpsilon)*(1-qu)) * n)) + w, min, max := a[k-1], a[lowerRank-1], a[upperRank-1] + if g := s.Query(qu); g < min || g > max { + t.Errorf("q=%f: want %v [%f,%f], got %v", qu, w, min, max, g) + } + } +} + +func populateStream(s *Stream) []float64 { + a := make([]float64, 0, 1e5+100) + for i := 0; i < cap(a); i++ { + v := rand.NormFloat64() + // Add 5% asymmetric outliers. + if i%20 == 0 { + v = v*v + 1 + } + s.Insert(v) + a = append(a, v) + } + return a +} + +func TestTargetedQuery(t *testing.T) { + rand.Seed(42) + s := NewTargeted(Targets) + a := populateStream(s) + verifyPercsWithAbsoluteEpsilon(t, a, s) +} + +func TestTargetedQuerySmallSampleSize(t *testing.T) { + rand.Seed(42) + s := NewTargeted(TargetsSmallEpsilon) + a := []float64{1, 2, 3, 4, 5} + for _, v := range a { + s.Insert(v) + } + verifyPercsWithAbsoluteEpsilon(t, a, s) + // If not yet flushed, results should be precise: + if !s.flushed() { + for φ, want := range map[float64]float64{ + 0.01: 1, + 0.10: 1, + 0.50: 3, + 0.90: 5, + 0.99: 5, + } { + if got := s.Query(φ); got != want { + t.Errorf("want %f for φ=%f, got %f", want, φ, got) + } + } + } +} + +func TestLowBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewLowBiased(RelativeEpsilon) + a := populateStream(s) + verifyLowPercsWithRelativeEpsilon(t, a, s) +} + +func TestHighBiasedQuery(t *testing.T) { + rand.Seed(42) + s := NewHighBiased(RelativeEpsilon) + a := populateStream(s) + verifyHighPercsWithRelativeEpsilon(t, a, s) +} + +// BrokenTestTargetedMerge is broken, see Merge doc comment. +func BrokenTestTargetedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewTargeted(Targets) + s2 := NewTargeted(Targets) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyPercsWithAbsoluteEpsilon(t, a, s1) +} + +// BrokenTestLowBiasedMerge is broken, see Merge doc comment. +func BrokenTestLowBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewLowBiased(RelativeEpsilon) + s2 := NewLowBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyLowPercsWithRelativeEpsilon(t, a, s2) +} + +// BrokenTestHighBiasedMerge is broken, see Merge doc comment. +func BrokenTestHighBiasedMerge(t *testing.T) { + rand.Seed(42) + s1 := NewHighBiased(RelativeEpsilon) + s2 := NewHighBiased(RelativeEpsilon) + a := populateStream(s1) + a = append(a, populateStream(s2)...) + s1.Merge(s2.Samples()) + verifyHighPercsWithRelativeEpsilon(t, a, s2) +} + +func TestUncompressed(t *testing.T) { + q := NewTargeted(Targets) + for i := 100; i > 0; i-- { + q.Insert(float64(i)) + } + if g := q.Count(); g != 100 { + t.Errorf("want count 100, got %d", g) + } + // Before compression, Query should have 100% accuracy. + for quantile := range Targets { + w := quantile * 100 + if g := q.Query(quantile); g != w { + t.Errorf("want %f, got %f", w, g) + } + } +} + +func TestUncompressedSamples(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.001}) + for i := 1; i <= 100; i++ { + q.Insert(float64(i)) + } + if g := q.Samples().Len(); g != 100 { + t.Errorf("want count 100, got %d", g) + } +} + +func TestUncompressedOne(t *testing.T) { + q := NewTargeted(map[float64]float64{0.99: 0.01}) + q.Insert(3.14) + if g := q.Query(0.90); g != 3.14 { + t.Error("want PI, got", g) + } +} + +func TestDefaults(t *testing.T) { + if g := NewTargeted(map[float64]float64{0.99: 0.001}).Query(0.99); g != 0 { + t.Errorf("want 0, got %f", g) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/.gitignore b/examples/servers/reading-list/vendor/github.com/go-kit/kit/.gitignore new file mode 100644 index 000000000..6062401c1 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/.gitignore @@ -0,0 +1,45 @@ +examples/addsvc/addsvc +examples/addsvc/client/client +examples/apigateway/apigateway +examples/profilesvc/profilesvc +examples/stringsvc1/stringsvc1 +examples/stringsvc2/stringsvc2 +examples/stringsvc3/stringsvc3 +*.coverprofile + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +_old* + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +# https://github.com/github/gitignore/blob/master/Global/Vim.gitignore +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/.travis.yml b/examples/servers/reading-list/vendor/github.com/go-kit/kit/.travis.yml new file mode 100644 index 000000000..7174414df --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/.travis.yml @@ -0,0 +1,8 @@ +language: go + +script: go test -race -v ./... + +go: + - 1.7.x + - 1.8.x + - tip diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/CONTRIBUTING.md b/examples/servers/reading-list/vendor/github.com/go-kit/kit/CONTRIBUTING.md new file mode 100644 index 000000000..c0751f85a --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributing + +First, thank you for contributing! We love and encourage pull requests from everyone. + +Before submitting major changes, here are a few guidelines to follow: + +1. Check the [open issues][issues] and [pull requests][prs] for existing discussions. +1. Open an [issue][issues] first, to discuss a new feature or enhancement. +1. Write tests, and make sure the test suite passes locally and on CI. +1. Open a pull request, and reference the relevant issue(s). +1. After receiving feedback, [squash your commits][squash] and add a [great commit message][message]. +1. Have fun! + +[issues]: https://github.com/go-kit/kit/issues +[prs]: https://github.com/go-kit/kit/pulls +[squash]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html +[message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html + diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/LICENSE b/examples/servers/reading-list/vendor/github.com/go-kit/kit/LICENSE new file mode 100644 index 000000000..9d83342ac --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Peter Bourgon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/README.md b/examples/servers/reading-list/vendor/github.com/go-kit/kit/README.md new file mode 100644 index 000000000..bbd82a084 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/README.md @@ -0,0 +1,120 @@ +# Go kit [![Circle CI](https://circleci.com/gh/go-kit/kit.svg?style=shield)](https://circleci.com/gh/go-kit/kit) [![Travis CI](https://travis-ci.org/go-kit/kit.svg?branch=master)](https://travis-ci.org/go-kit/kit) [![GoDoc](https://godoc.org/github.com/go-kit/kit?status.svg)](https://godoc.org/github.com/go-kit/kit) [![Coverage Status](https://coveralls.io/repos/go-kit/kit/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-kit/kit?branch=master) [![Go Report Card](https://goreportcard.com/badge/go-kit/kit)](https://goreportcard.com/report/go-kit/kit) [![Sourcegraph](https://sourcegraph.com/github.com/go-kit/kit/-/badge.svg)](https://sourcegraph.com/github.com/go-kit/kit?badge) + +**Go kit** is a **distributed programming toolkit** for building microservices +in large organizations. We solve common problems in distributed systems, so +you can focus on your business logic. + +- Website: [gokit.io](https://gokit.io) +- Mailing list: [go-kit](https://groups.google.com/forum/#!forum/go-kit) +- Slack: [gophers.slack.com](https://gophers.slack.com) **#go-kit** ([invite](https://gophersinvite.herokuapp.com/)) + +## Motivation + +Go has emerged as the language of the server, but it remains underrepresented +in large, consumer-focused tech companies like Facebook, Twitter, Netflix, and +SoundCloud. These organizations have largely adopted JVM-based stacks for +their business logic, owing in large part to libraries and ecosystems that +directly support their microservice architectures. + +To reach its next level of success, Go needs more than simple primitives and +idioms. It needs a comprehensive toolkit, for coherent distributed programming +in the large. Go kit is a set of packages and best practices, which provide a +comprehensive, robust, and trustable way of building microservices for +organizations of any size. + +For more details, see + [the website](https://gokit.io), + [the motivating blog post](http://peter.bourgon.org/go-kit/) and + [the video of the talk](https://www.youtube.com/watch?v=iFR_7AKkJFU). +See also the + [Go kit talk at GopherCon 2015](https://www.youtube.com/watch?v=1AjaZi4QuGo). + +## Goals + +- Operate in a heterogeneous SOA — expect to interact with mostly non-Go-kit services +- RPC as the primary messaging pattern +- Pluggable serialization and transport — not just JSON over HTTP +- Operate within existing infrastructures — no mandates for specific tools or technologies + +## Non-goals + +- Supporting messaging patterns other than RPC (for now) — e.g. MPI, pub/sub, CQRS, etc. +- Re-implementing functionality that can be provided by adapting existing software +- Having opinions on operational concerns: deployment, configuration, process supervision, orchestration, etc. + +## Contributing + +Please see [CONTRIBUTING.md](/CONTRIBUTING.md). +Thank you, [contributors](https://github.com/go-kit/kit/graphs/contributors)! + +## Dependency management + +Go kit is a library, designed to be imported into a binary package. +Vendoring is currently the best way for binary package authors + to ensure reliable, reproducible builds. +Therefore, we strongly recommend our users use vendoring for all of their dependencies, + including Go kit. +To avoid compatibility and availability issues, + Go kit doesn't vendor its own dependencies, + and doesn't recommend use of third-party import proxies. + +There are several tools which make vendoring easier, including + [gb](http://getgb.io), + [glide](https://github.com/Masterminds/glide), + [gvt](https://github.com/FiloSottile/gvt), + [govendor](https://github.com/kardianos/govendor), and + [vendetta](https://github.com/dpw/vendetta). +In addition, Go kit uses a variety of continuous integration providers + to find and fix compatibility problems as soon as they occur. + +## Related projects + +Projects with a ★ have had particular influence on Go kit's design (or vice-versa). + +### Service frameworks + +- [gizmo](https://github.com/nytimes/gizmo), a microservice toolkit from The New York Times ★ +- [go-micro](https://github.com/myodc/go-micro), a microservices client/server library ★ +- [gotalk](https://github.com/rsms/gotalk), async peer communication protocol & library +- [Kite](https://github.com/koding/kite), a micro-service framework +- [gocircuit](https://github.com/gocircuit/circuit), dynamic cloud orchestration + +### Individual components + +- [afex/hystrix-go](https://github.com/afex/hystrix-go), client-side latency and fault tolerance library +- [armon/go-metrics](https://github.com/armon/go-metrics), library for exporting performance and runtime metrics to external metrics systems +- [codahale/lunk](https://github.com/codahale/lunk), structured logging in the style of Google's Dapper or Twitter's Zipkin +- [eapache/go-resiliency](https://github.com/eapache/go-resiliency), resiliency patterns +- [sasbury/logging](https://github.com/sasbury/logging), a tagged style of logging +- [grpc/grpc-go](https://github.com/grpc/grpc-go), HTTP/2 based RPC +- [inconshreveable/log15](https://github.com/inconshreveable/log15), simple, powerful logging for Go ★ +- [mailgun/vulcand](https://github.com/vulcand/vulcand), programmatic load balancer backed by etcd +- [mattheath/phosphor](https://github.com/mondough/phosphor), distributed system tracing +- [pivotal-golang/lager](https://github.com/pivotal-golang/lager), an opinionated logging library +- [rubyist/circuitbreaker](https://github.com/rubyist/circuitbreaker), circuit breaker library +- [Sirupsen/logrus](https://github.com/Sirupsen/logrus), structured, pluggable logging for Go ★ +- [sourcegraph/appdash](https://github.com/sourcegraph/appdash), application tracing system based on Google's Dapper +- [spacemonkeygo/monitor](https://github.com/spacemonkeygo/monitor), data collection, monitoring, instrumentation, and Zipkin client library +- [streadway/handy](https://github.com/streadway/handy), net/http handler filters +- [vitess/rpcplus](https://godoc.org/github.com/youtube/vitess/go/rpcplus), package rpc + context.Context +- [gdamore/mangos](https://github.com/gdamore/mangos), nanomsg implementation in pure Go + +### Web frameworks + +- [Gorilla](http://www.gorillatoolkit.org) +- [Gin](https://gin-gonic.github.io/gin/) +- [Negroni](https://github.com/codegangsta/negroni) +- [Goji](https://github.com/zenazn/goji) +- [Martini](https://github.com/go-martini/martini) +- [Beego](http://beego.me/) +- [Revel](https://revel.github.io/) (considered [harmful](https://github.com/go-kit/kit/issues/350)) + +## Additional reading + +- [Architecting for the Cloud](http://fr.slideshare.net/stonse/architecting-for-the-cloud-using-netflixoss-codemash-workshop-29852233) — Netflix +- [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://research.google.com/pubs/pub36356.html) — Google +- [Your Server as a Function](http://monkey.org/~marius/funsrv.pdf) (PDF) — Twitter + +--- + +Development supported by [DigitalOcean](https://digitalocean.com). diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/ROADMAP.md b/examples/servers/reading-list/vendor/github.com/go-kit/kit/ROADMAP.md new file mode 100644 index 000000000..5c462aa21 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/ROADMAP.md @@ -0,0 +1,17 @@ +# Roadmap + +This is a coarse-grained roadmap of Go kit development direction in the short +to mid-term future. It will be kept reasonably up-to-date by the project +maintainers. Suggest new ideas, enhancements, and features using the standard +[issues](https://github.com/go-kit/kit/issues) model. + +## Prioritized + +1. kitgen code generation (#308, #70) +1. package pubsub (#298, #295) + +## Unprioritized + +- package log/levels refactor (#250, #269, #252) +- package auth/jwt (#255) + diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/circle.yml b/examples/servers/reading-list/vendor/github.com/go-kit/kit/circle.yml new file mode 100644 index 000000000..ad9e45af3 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/circle.yml @@ -0,0 +1,27 @@ +machine: + pre: + - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0 + - sudo rm -rf /usr/local/go + - curl -sSL https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz | sudo tar xz -C /usr/local + services: + - docker + +dependencies: + pre: + - sudo curl -L "https://github.com/docker/compose/releases/download/1.10.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose + - sudo chmod +x /usr/local/bin/docker-compose + - docker-compose -f docker-compose-integration.yml up -d --force-recreate + +test: + pre: + - mkdir -p /home/ubuntu/.go_workspace/src/github.com/go-kit + - mv /home/ubuntu/kit /home/ubuntu/.go_workspace/src/github.com/go-kit + - ln -s /home/ubuntu/.go_workspace/src/github.com/go-kit/kit /home/ubuntu/kit + - go get github.com/go-kit/kit/... + override: + - go test -v -race -tags integration github.com/go-kit/kit/...: + environment: + ETCD_ADDR: http://localhost:2379 + CONSUL_ADDR: localhost:8500 + ZK_ADDR: localhost:2181 + EUREKA_ADDR: http://localhost:8761/eureka diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/coverage.bash b/examples/servers/reading-list/vendor/github.com/go-kit/kit/coverage.bash new file mode 100755 index 000000000..f4b0524b5 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/coverage.bash @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# This script runs the cover tool on all packages with test files. If you set a +# WEB environment variable, it will additionally open the web-based coverage +# visualizer for each package. + +set -e + +function go_files { find . -name '*_test.go' ; } +function filter { grep -v '/_' ; } +function remove_relative_prefix { sed -e 's/^\.\///g' ; } + +function directories { + go_files | filter | remove_relative_prefix | while read f + do + dirname $f + done +} + +function unique_directories { directories | sort | uniq ; } + +PATHS=${1:-$(unique_directories)} + +function report { + for path in $PATHS + do + go test -coverprofile=$path/cover.coverprofile ./$path + done +} + +function combine { + gover +} + +function clean { + find . -name cover.coverprofile | xargs rm +} + +report +combine +clean + +if [ -n "${WEB+x}" ] +then + go tool cover -html=gover.coverprofile +fi + diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/docker-compose-integration.yml b/examples/servers/reading-list/vendor/github.com/go-kit/kit/docker-compose-integration.yml new file mode 100644 index 000000000..287d97db0 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/docker-compose-integration.yml @@ -0,0 +1,22 @@ +version: '2' +services: + etcd: + image: quay.io/coreos/etcd + ports: + - "2379:2379" + command: /usr/local/bin/etcd -advertise-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 -listen-client-urls "http://0.0.0.0:2379,http://0.0.0.0:4001" + consul: + image: progrium/consul + ports: + - "8500:8500" + command: -server -bootstrap + zk: + image: zookeeper + ports: + - "2181:2181" + eureka: + image: springcloud/eureka + environment: + eureka.server.responseCacheUpdateIntervalMs: 1000 + ports: + - "8761:8761" diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/doc.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/doc.go new file mode 100644 index 000000000..84e27b95d --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/doc.go @@ -0,0 +1,5 @@ +// Package endpoint defines an abstraction for RPCs. +// +// Endpoints are a fundamental building block for many Go kit components. +// Endpoints are implemented by servers, and called by clients. +package endpoint diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint.go new file mode 100644 index 000000000..1b64f50ed --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint.go @@ -0,0 +1,28 @@ +package endpoint + +import ( + "context" +) + +// Endpoint is the fundamental building block of servers and clients. +// It represents a single RPC method. +type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error) + +// Nop is an endpoint that does nothing and returns a nil error. +// Useful for tests. +func Nop(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil } + +// Middleware is a chainable behavior modifier for endpoints. +type Middleware func(Endpoint) Endpoint + +// Chain is a helper function for composing middlewares. Requests will +// traverse them in the order they're declared. That is, the first middleware +// is treated as the outermost middleware. +func Chain(outer Middleware, others ...Middleware) Middleware { + return func(next Endpoint) Endpoint { + for i := len(others) - 1; i >= 0; i-- { // reverse + next = others[i](next) + } + return outer(next) + } +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint_example_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint_example_test.go new file mode 100644 index 000000000..e95062305 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/endpoint/endpoint_example_test.go @@ -0,0 +1,49 @@ +package endpoint_test + +import ( + "context" + "fmt" + + "github.com/go-kit/kit/endpoint" +) + +func ExampleChain() { + e := endpoint.Chain( + annotate("first"), + annotate("second"), + annotate("third"), + )(myEndpoint) + + if _, err := e(ctx, req); err != nil { + panic(err) + } + + // Output: + // first pre + // second pre + // third pre + // my endpoint! + // third post + // second post + // first post +} + +var ( + ctx = context.Background() + req = struct{}{} +) + +func annotate(s string) endpoint.Middleware { + return func(next endpoint.Endpoint) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (interface{}, error) { + fmt.Println(s, "pre") + defer fmt.Println(s, "post") + return next(ctx, request) + } + } +} + +func myEndpoint(context.Context, interface{}) (interface{}, error) { + fmt.Println("my endpoint!") + return struct{}{}, nil +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/lint b/examples/servers/reading-list/vendor/github.com/go-kit/kit/lint new file mode 100755 index 000000000..12e307273 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/lint @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +if [ ! $(command -v gometalinter) ] +then + go get github.com/alecthomas/gometalinter + gometalinter --update --install +fi + +time gometalinter \ + --exclude='error return value not checked.*(Close|Log|Print).*\(errcheck\)$' \ + --exclude='.*_test\.go:.*error return value not checked.*\(errcheck\)$' \ + --exclude='/thrift/' \ + --exclude='/pb/' \ + --exclude='no args in Log call \(vet\)' \ + --disable=dupl \ + --disable=aligncheck \ + --disable=gotype \ + --cyclo-over=20 \ + --tests \ + --concurrency=2 \ + --deadline=300s \ + ./... diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/README.md b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/README.md new file mode 100644 index 000000000..7222f8009 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/README.md @@ -0,0 +1,147 @@ +# package log + +`package log` provides a minimal interface for structured logging in services. +It may be wrapped to encode conventions, enforce type-safety, provide leveled +logging, and so on. It can be used for both typical application log events, +and log-structured data streams. + +## Structured logging + +Structured logging is, basically, conceding to the reality that logs are +_data_, and warrant some level of schematic rigor. Using a stricter, +key/value-oriented message format for our logs, containing contextual and +semantic information, makes it much easier to get insight into the +operational activity of the systems we build. Consequently, `package log` is +of the strong belief that "[the benefits of structured logging outweigh the +minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)". + +Migrating from unstructured to structured logging is probably a lot easier +than you'd expect. + +```go +// Unstructured +log.Printf("HTTP server listening on %s", addr) + +// Structured +logger.Log("transport", "HTTP", "addr", addr, "msg", "listening") +``` + +## Usage + +### Typical application logging + +```go +w := log.NewSyncWriter(os.Stderr) +logger := log.NewLogfmtLogger(w) +logger.Log("question", "what is the meaning of life?", "answer", 42) + +// Output: +// question="what is the meaning of life?" answer=42 +``` + +### Contextual Loggers + +```go +func main() { + var logger log.Logger + logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) + logger = log.With(logger, "instance_id", 123) + + logger.Log("msg", "starting") + NewWorker(log.With(logger, "component", "worker")).Run() + NewSlacker(log.With(logger, "component", "slacker")).Run() +} + +// Output: +// instance_id=123 msg=starting +// instance_id=123 component=worker msg=running +// instance_id=123 component=slacker msg=running +``` + +### Interact with stdlib logger + +Redirect stdlib logger to Go kit logger. + +```go +import ( + "os" + stdlog "log" + kitlog "github.com/go-kit/kit/log" +) + +func main() { + logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout)) + stdlog.SetOutput(kitlog.NewStdlibAdapter(logger)) + stdlog.Print("I sure like pie") +} + +// Output: +// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"} +``` + +Or, if, for legacy reasons, you need to pipe all of your logging through the +stdlib log package, you can redirect Go kit logger to the stdlib logger. + +```go +logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{}) +logger.Log("legacy", true, "msg", "at least it's something") + +// Output: +// 2016/01/01 12:34:56 legacy=true msg="at least it's something" +``` + +### Timestamps and callers + +```go +var logger log.Logger +logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr)) +logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) + +logger.Log("msg", "hello") + +// Output: +// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello +``` + +## Supported output formats + +- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write)) +- JSON + +## Enhancements + +`package log` is centered on the one-method Logger interface. + +```go +type Logger interface { + Log(keyvals ...interface{}) error +} +``` + +This interface, and its supporting code like is the product of much iteration +and evaluation. For more details on the evolution of the Logger interface, +see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1), +a talk by [Chris Hines](https://github.com/ChrisHines). +Also, please see +[#63](https://github.com/go-kit/kit/issues/63), +[#76](https://github.com/go-kit/kit/pull/76), +[#131](https://github.com/go-kit/kit/issues/131), +[#157](https://github.com/go-kit/kit/pull/157), +[#164](https://github.com/go-kit/kit/issues/164), and +[#252](https://github.com/go-kit/kit/pull/252) +to review historical conversations about package log and the Logger interface. + +Value-add packages and suggestions, +like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/kit/log/level), +are of course welcome. Good proposals should + +- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/kit/log#With), +- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/kit/log#Caller) in any wrapped contextual loggers, and +- Be friendly to packages that accept only an unadorned log.Logger. + +## Benchmarks & comparisons + +There are a few Go logging benchmarks and comparisons that include Go kit's package log. + +- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log +- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/benchmark_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/benchmark_test.go new file mode 100644 index 000000000..126bfa5ae --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/benchmark_test.go @@ -0,0 +1,21 @@ +package log_test + +import ( + "testing" + + "github.com/go-kit/kit/log" +) + +func benchmarkRunner(b *testing.B, logger log.Logger, f func(log.Logger)) { + lc := log.With(logger, "common_key", "common_value") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + f(lc) + } +} + +var ( + baseMessage = func(logger log.Logger) { logger.Log("foo_key", "foo_value") } + withMessage = func(logger log.Logger) { log.With(logger, "a", "b").Log("c", "d") } +) diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/concurrency_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/concurrency_test.go new file mode 100644 index 000000000..95a749e77 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/concurrency_test.go @@ -0,0 +1,40 @@ +package log_test + +import ( + "math" + "testing" + + "github.com/go-kit/kit/log" +) + +// These test are designed to be run with the race detector. + +func testConcurrency(t *testing.T, logger log.Logger, total int) { + n := int(math.Sqrt(float64(total))) + share := total / n + + errC := make(chan error, n) + + for i := 0; i < n; i++ { + go func() { + errC <- spam(logger, share) + }() + } + + for i := 0; i < n; i++ { + err := <-errC + if err != nil { + t.Fatalf("concurrent logging error: %v", err) + } + } +} + +func spam(logger log.Logger, count int) error { + for i := 0; i < count; i++ { + err := logger.Log("key", i) + if err != nil { + return err + } + } + return nil +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/doc.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/doc.go new file mode 100644 index 000000000..918c0af46 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/doc.go @@ -0,0 +1,116 @@ +// Package log provides a structured logger. +// +// Structured logging produces logs easily consumed later by humans or +// machines. Humans might be interested in debugging errors, or tracing +// specific requests. Machines might be interested in counting interesting +// events, or aggregating information for off-line processing. In both cases, +// it is important that the log messages are structured and actionable. +// Package log is designed to encourage both of these best practices. +// +// Basic Usage +// +// The fundamental interface is Logger. Loggers create log events from +// key/value data. The Logger interface has a single method, Log, which +// accepts a sequence of alternating key/value pairs, which this package names +// keyvals. +// +// type Logger interface { +// Log(keyvals ...interface{}) error +// } +// +// Here is an example of a function using a Logger to create log events. +// +// func RunTask(task Task, logger log.Logger) string { +// logger.Log("taskID", task.ID, "event", "starting task") +// ... +// logger.Log("taskID", task.ID, "event", "task complete") +// } +// +// The keys in the above example are "taskID" and "event". The values are +// task.ID, "starting task", and "task complete". Every key is followed +// immediately by its value. +// +// Keys are usually plain strings. Values may be any type that has a sensible +// encoding in the chosen log format. With structured logging it is a good +// idea to log simple values without formatting them. This practice allows +// the chosen logger to encode values in the most appropriate way. +// +// Contextual Loggers +// +// A contextual logger stores keyvals that it includes in all log events. +// Building appropriate contextual loggers reduces repetition and aids +// consistency in the resulting log output. With and WithPrefix add context to +// a logger. We can use With to improve the RunTask example. +// +// func RunTask(task Task, logger log.Logger) string { +// logger = log.With(logger, "taskID", task.ID) +// logger.Log("event", "starting task") +// ... +// taskHelper(task.Cmd, logger) +// ... +// logger.Log("event", "task complete") +// } +// +// The improved version emits the same log events as the original for the +// first and last calls to Log. Passing the contextual logger to taskHelper +// enables each log event created by taskHelper to include the task.ID even +// though taskHelper does not have access to that value. Using contextual +// loggers this way simplifies producing log output that enables tracing the +// life cycle of individual tasks. (See the Contextual example for the full +// code of the above snippet.) +// +// Dynamic Contextual Values +// +// A Valuer function stored in a contextual logger generates a new value each +// time an event is logged. The Valuer example demonstrates how this feature +// works. +// +// Valuers provide the basis for consistently logging timestamps and source +// code location. The log package defines several valuers for that purpose. +// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and +// DefaultCaller. A common logger initialization sequence that ensures all log +// entries contain a timestamp and source location looks like this: +// +// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller) +// +// Concurrent Safety +// +// Applications with multiple goroutines want each log event written to the +// same logger to remain separate from other log events. Package log provides +// two simple solutions for concurrent safe logging. +// +// NewSyncWriter wraps an io.Writer and serializes each call to its Write +// method. Using a SyncWriter has the benefit that the smallest practical +// portion of the logging logic is performed within a mutex, but it requires +// the formatting Logger to make only one call to Write per log event. +// +// NewSyncLogger wraps any Logger and serializes each call to its Log method. +// Using a SyncLogger has the benefit that it guarantees each log event is +// handled atomically within the wrapped logger, but it typically serializes +// both the formatting and output logic. Use a SyncLogger if the formatting +// logger may perform multiple writes per log event. +// +// Error Handling +// +// This package relies on the practice of wrapping or decorating loggers with +// other loggers to provide composable pieces of functionality. It also means +// that Logger.Log must return an error because some +// implementations—especially those that output log data to an io.Writer—may +// encounter errors that cannot be handled locally. This in turn means that +// Loggers that wrap other loggers should return errors from the wrapped +// logger up the stack. +// +// Fortunately, the decorator pattern also provides a way to avoid the +// necessity to check for errors every time an application calls Logger.Log. +// An application required to panic whenever its Logger encounters +// an error could initialize its logger as follows. +// +// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) +// logger := log.LoggerFunc(func(keyvals ...interface{}) error { +// if err := fmtlogger.Log(keyvals...); err != nil { +// panic(err) +// } +// return nil +// }) +package log diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/example_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/example_test.go new file mode 100644 index 000000000..976677489 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/example_test.go @@ -0,0 +1,137 @@ +package log_test + +import ( + "math/rand" + "os" + "sync" + "time" + + "github.com/go-kit/kit/log" +) + +func Example_basic() { + logger := log.NewLogfmtLogger(os.Stdout) + + type Task struct { + ID int + } + + RunTask := func(task Task, logger log.Logger) { + logger.Log("taskID", task.ID, "event", "starting task") + + logger.Log("taskID", task.ID, "event", "task complete") + } + + RunTask(Task{ID: 1}, logger) + + // Output: + // taskID=1 event="starting task" + // taskID=1 event="task complete" +} + +func Example_contextual() { + logger := log.NewLogfmtLogger(os.Stdout) + + type Task struct { + ID int + Cmd string + } + + taskHelper := func(cmd string, logger log.Logger) { + // execute(cmd) + logger.Log("cmd", cmd, "dur", 42*time.Millisecond) + } + + RunTask := func(task Task, logger log.Logger) { + logger = log.With(logger, "taskID", task.ID) + logger.Log("event", "starting task") + + taskHelper(task.Cmd, logger) + + logger.Log("event", "task complete") + } + + RunTask(Task{ID: 1, Cmd: "echo Hello, world!"}, logger) + + // Output: + // taskID=1 event="starting task" + // taskID=1 cmd="echo Hello, world!" dur=42ms + // taskID=1 event="task complete" +} + +func Example_valuer() { + logger := log.NewLogfmtLogger(os.Stdout) + + count := 0 + counter := func() interface{} { + count++ + return count + } + + logger = log.With(logger, "count", log.Valuer(counter)) + + logger.Log("call", "first") + logger.Log("call", "second") + + // Output: + // count=1 call=first + // count=2 call=second +} + +func Example_debugInfo() { + logger := log.NewLogfmtLogger(os.Stdout) + + // make time predictable for this test + baseTime := time.Date(2015, time.February, 3, 10, 0, 0, 0, time.UTC) + mockTime := func() time.Time { + baseTime = baseTime.Add(time.Second) + return baseTime + } + + logger = log.With(logger, "time", log.Timestamp(mockTime), "caller", log.DefaultCaller) + + logger.Log("call", "first") + logger.Log("call", "second") + + // ... + + logger.Log("call", "third") + + // Output: + // time=2015-02-03T10:00:01Z caller=example_test.go:93 call=first + // time=2015-02-03T10:00:02Z caller=example_test.go:94 call=second + // time=2015-02-03T10:00:03Z caller=example_test.go:98 call=third +} + +func Example_syncWriter() { + w := log.NewSyncWriter(os.Stdout) + logger := log.NewLogfmtLogger(w) + + type Task struct { + ID int + } + + var wg sync.WaitGroup + + RunTask := func(task Task, logger log.Logger) { + logger.Log("taskID", task.ID, "event", "starting task") + + time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond) + + logger.Log("taskID", task.ID, "event", "task complete") + wg.Done() + } + + wg.Add(2) + + go RunTask(Task{ID: 1}, logger) + go RunTask(Task{ID: 2}, logger) + + wg.Wait() + + // Unordered output: + // taskID=1 event="starting task" + // taskID=2 event="starting task" + // taskID=1 event="task complete" + // taskID=2 event="task complete" +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger.go new file mode 100644 index 000000000..231e09955 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger.go @@ -0,0 +1,92 @@ +package log + +import ( + "encoding" + "encoding/json" + "fmt" + "io" + "reflect" +) + +type jsonLogger struct { + io.Writer +} + +// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a +// single JSON object. Each log event produces no more than one call to +// w.Write. The passed Writer must be safe for concurrent use by multiple +// goroutines if the returned Logger will be used concurrently. +func NewJSONLogger(w io.Writer) Logger { + return &jsonLogger{w} +} + +func (l *jsonLogger) Log(keyvals ...interface{}) error { + n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd + m := make(map[string]interface{}, n) + for i := 0; i < len(keyvals); i += 2 { + k := keyvals[i] + var v interface{} = ErrMissingValue + if i+1 < len(keyvals) { + v = keyvals[i+1] + } + merge(m, k, v) + } + return json.NewEncoder(l.Writer).Encode(m) +} + +func merge(dst map[string]interface{}, k, v interface{}) { + var key string + switch x := k.(type) { + case string: + key = x + case fmt.Stringer: + key = safeString(x) + default: + key = fmt.Sprint(x) + } + if x, ok := v.(error); ok { + v = safeError(x) + } + + // We want json.Marshaler and encoding.TextMarshaller to take priority over + // err.Error() and v.String(). But json.Marshall (called later) does that by + // default so we force a no-op if it's one of those 2 case. + switch x := v.(type) { + case json.Marshaler: + case encoding.TextMarshaler: + case error: + v = safeError(x) + case fmt.Stringer: + v = safeString(x) + } + + dst[key] = v +} + +func safeString(str fmt.Stringer) (s string) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { + s = "NULL" + } else { + panic(panicVal) + } + } + }() + s = str.String() + return +} + +func safeError(err error) (s interface{}) { + defer func() { + if panicVal := recover(); panicVal != nil { + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + s = nil + } else { + panic(panicVal) + } + } + }() + s = err.Error() + return +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger_test.go new file mode 100644 index 000000000..00e691005 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/json_logger_test.go @@ -0,0 +1,158 @@ +package log_test + +import ( + "bytes" + "errors" + "io/ioutil" + "testing" + + "github.com/go-kit/kit/log" +) + +func TestJSONLoggerCaller(t *testing.T) { + t.Parallel() + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + logger = log.With(logger, "caller", log.DefaultCaller) + + if err := logger.Log(); err != nil { + t.Fatal(err) + } + if want, have := `{"caller":"json_logger_test.go:18"}`+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } +} + +func TestJSONLogger(t *testing.T) { + t.Parallel() + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + if err := logger.Log("err", errors.New("err"), "m", map[string]int{"0": 0}, "a", []int{1, 2, 3}); err != nil { + t.Fatal(err) + } + if want, have := `{"a":[1,2,3],"err":"err","m":{"0":0}}`+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } +} + +func TestJSONLoggerMissingValue(t *testing.T) { + t.Parallel() + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + if err := logger.Log("k"); err != nil { + t.Fatal(err) + } + if want, have := `{"k":"(MISSING)"}`+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } +} + +func TestJSONLoggerNilStringerKey(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + if err := logger.Log((*stringer)(nil), "v"); err != nil { + t.Fatal(err) + } + if want, have := `{"NULL":"v"}`+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } +} + +func TestJSONLoggerNilErrorValue(t *testing.T) { + t.Parallel() + + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + if err := logger.Log("err", (*stringError)(nil)); err != nil { + t.Fatal(err) + } + if want, have := `{"err":null}`+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } +} + +// aller implements json.Marshaler, encoding.TextMarshaler, and fmt.Stringer. +type aller struct{} + +func (aller) MarshalJSON() ([]byte, error) { + return []byte("\"json\""), nil +} + +func (aller) MarshalText() ([]byte, error) { + return []byte("text"), nil +} + +func (aller) String() string { + return "string" +} + +// textstringer implements encoding.TextMarshaler and fmt.Stringer. +type textstringer struct{} + +func (textstringer) MarshalText() ([]byte, error) { + return []byte("text"), nil +} + +func (textstringer) String() string { + return "string" +} + +func TestJSONLoggerStringValue(t *testing.T) { + t.Parallel() + tests := []struct { + v interface{} + expected string + }{ + { + v: aller{}, + expected: `{"v":"json"}`, + }, + { + v: textstringer{}, + expected: `{"v":"text"}`, + }, + { + v: stringer("string"), + expected: `{"v":"string"}`, + }, + } + + for _, test := range tests { + buf := &bytes.Buffer{} + logger := log.NewJSONLogger(buf) + if err := logger.Log("v", test.v); err != nil { + t.Fatal(err) + } + + if want, have := test.expected+"\n", buf.String(); want != have { + t.Errorf("\nwant %#v\nhave %#v", want, have) + } + } +} + +type stringer string + +func (s stringer) String() string { + return string(s) +} + +type stringError string + +func (s stringError) Error() string { + return string(s) +} + +func BenchmarkJSONLoggerSimple(b *testing.B) { + benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), baseMessage) +} + +func BenchmarkJSONLoggerContextual(b *testing.B) { + benchmarkRunner(b, log.NewJSONLogger(ioutil.Discard), withMessage) +} + +func TestJSONLoggerConcurrency(t *testing.T) { + t.Parallel() + testConcurrency(t, log.NewJSONLogger(ioutil.Discard), 10000) +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log.go new file mode 100644 index 000000000..66a9e2fde --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log.go @@ -0,0 +1,135 @@ +package log + +import "errors" + +// Logger is the fundamental interface for all log operations. Log creates a +// log event from keyvals, a variadic sequence of alternating keys and values. +// Implementations must be safe for concurrent use by multiple goroutines. In +// particular, any implementation of Logger that appends to keyvals or +// modifies or retains any of its elements must make a copy first. +type Logger interface { + Log(keyvals ...interface{}) error +} + +// ErrMissingValue is appended to keyvals slices with odd length to substitute +// the missing value. +var ErrMissingValue = errors.New("(MISSING)") + +// With returns a new contextual logger with keyvals prepended to those passed +// to calls to Log. If logger is also a contextual logger created by With or +// WithPrefix, keyvals is appended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func With(logger Logger, keyvals ...interface{}) Logger { + if len(keyvals) == 0 { + return logger + } + l := newContext(logger) + kvs := append(l.keyvals, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + return &context{ + logger: l.logger, + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + keyvals: kvs[:len(kvs):len(kvs)], + hasValuer: l.hasValuer || containsValuer(keyvals), + } +} + +// WithPrefix returns a new contextual logger with keyvals prepended to those +// passed to calls to Log. If logger is also a contextual logger created by +// With or WithPrefix, keyvals is prepended to the existing context. +// +// The returned Logger replaces all value elements (odd indexes) containing a +// Valuer with their generated value for each call to its Log method. +func WithPrefix(logger Logger, keyvals ...interface{}) Logger { + if len(keyvals) == 0 { + return logger + } + l := newContext(logger) + // Limiting the capacity of the stored keyvals ensures that a new + // backing array is created if the slice must grow in Log or With. + // Using the extra capacity without copying risks a data race that + // would violate the Logger interface contract. + n := len(l.keyvals) + len(keyvals) + if len(keyvals)%2 != 0 { + n++ + } + kvs := make([]interface{}, 0, n) + kvs = append(kvs, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + kvs = append(kvs, l.keyvals...) + return &context{ + logger: l.logger, + keyvals: kvs, + hasValuer: l.hasValuer || containsValuer(keyvals), + } +} + +// context is the Logger implementation returned by With and WithPrefix. It +// wraps a Logger and holds keyvals that it includes in all log events. Its +// Log method calls bindValues to generate values for each Valuer in the +// context keyvals. +// +// A context must always have the same number of stack frames between calls to +// its Log method and the eventual binding of Valuers to their value. This +// requirement comes from the functional requirement to allow a context to +// resolve application call site information for a Caller stored in the +// context. To do this we must be able to predict the number of logging +// functions on the stack when bindValues is called. +// +// Two implementation details provide the needed stack depth consistency. +// +// 1. newContext avoids introducing an additional layer when asked to +// wrap another context. +// 2. With and WithPrefix avoid introducing an additional layer by +// returning a newly constructed context with a merged keyvals rather +// than simply wrapping the existing context. +type context struct { + logger Logger + keyvals []interface{} + hasValuer bool +} + +func newContext(logger Logger) *context { + if c, ok := logger.(*context); ok { + return c + } + return &context{logger: logger} +} + +// Log replaces all value elements (odd indexes) containing a Valuer in the +// stored context with their generated value, appends keyvals, and passes the +// result to the wrapped Logger. +func (l *context) Log(keyvals ...interface{}) error { + kvs := append(l.keyvals, keyvals...) + if len(kvs)%2 != 0 { + kvs = append(kvs, ErrMissingValue) + } + if l.hasValuer { + // If no keyvals were appended above then we must copy l.keyvals so + // that future log events will reevaluate the stored Valuers. + if len(keyvals) == 0 { + kvs = append([]interface{}{}, l.keyvals...) + } + bindValues(kvs[:len(l.keyvals)]) + } + return l.logger.Log(kvs...) +} + +// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If +// f is a function with the appropriate signature, LoggerFunc(f) is a Logger +// object that calls f. +type LoggerFunc func(...interface{}) error + +// Log implements Logger by calling f(keyvals...). +func (f LoggerFunc) Log(keyvals ...interface{}) error { + return f(keyvals...) +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log_test.go new file mode 100644 index 000000000..1bf29727e --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/log_test.go @@ -0,0 +1,191 @@ +package log_test + +import ( + "bytes" + "fmt" + "sync" + "testing" + + "github.com/go-kit/kit/log" + "github.com/go-stack/stack" +) + +func TestContext(t *testing.T) { + t.Parallel() + buf := &bytes.Buffer{} + logger := log.NewLogfmtLogger(buf) + + kvs := []interface{}{"a", 123} + lc := log.With(logger, kvs...) + kvs[1] = 0 // With should copy its key values + + lc = log.With(lc, "b", "c") // With should stack + if err := lc.Log("msg", "message"); err != nil { + t.Fatal(err) + } + if want, have := "a=123 b=c msg=message\n", buf.String(); want != have { + t.Errorf("\nwant: %shave: %s", want, have) + } + + buf.Reset() + lc = log.WithPrefix(lc, "p", "first") + if err := lc.Log("msg", "message"); err != nil { + t.Fatal(err) + } + if want, have := "p=first a=123 b=c msg=message\n", buf.String(); want != have { + t.Errorf("\nwant: %shave: %s", want, have) + } +} + +func TestContextMissingValue(t *testing.T) { + t.Parallel() + var output []interface{} + logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { + output = keyvals + return nil + })) + + log.WithPrefix(log.With(logger, "k1"), "k0").Log("k2") + if want, have := 6, len(output); want != have { + t.Errorf("want len(output) == %v, have %v", want, have) + } + for i := 1; i < 6; i += 2 { + if want, have := log.ErrMissingValue, output[i]; want != have { + t.Errorf("want output[%d] == %#v, have %#v", i, want, have) + } + } +} + +// Test that context.Log has a consistent function stack depth when binding +// Valuers, regardless of how many times With has been called. +func TestContextStackDepth(t *testing.T) { + t.Parallel() + fn := fmt.Sprintf("%n", stack.Caller(0)) + + var output []interface{} + + logger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error { + output = keyvals + return nil + })) + + stackValuer := log.Valuer(func() interface{} { + for i, c := range stack.Trace() { + if fmt.Sprintf("%n", c) == fn { + return i + } + } + t.Fatal("Test function not found in stack trace.") + return nil + }) + + logger = log.With(logger, "stack", stackValuer) + + // Call through interface to get baseline. + logger.Log("k", "v") + want := output[1].(int) + + for len(output) < 10 { + logger.Log("k", "v") + if have := output[1]; have != want { + t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) + } + + wrapped := log.With(logger) + wrapped.Log("k", "v") + if have := output[1]; have != want { + t.Errorf("%d Withs: have %v, want %v", len(output)/2-1, have, want) + } + + logger = log.With(logger, "k", "v") + } +} + +// Test that With returns a Logger safe for concurrent use. This test +// validates that the stored logging context does not get corrupted when +// multiple clients concurrently log additional keyvals. +// +// This test must be run with go test -cpu 2 (or more) to achieve its goal. +func TestWithConcurrent(t *testing.T) { + // Create some buckets to count how many events each goroutine logs. + const goroutines = 8 + counts := [goroutines]int{} + + // This logger extracts a goroutine id from the last value field and + // increments the referenced bucket. + logger := log.LoggerFunc(func(kv ...interface{}) error { + goroutine := kv[len(kv)-1].(int) + counts[goroutine]++ + return nil + }) + + // With must be careful about handling slices that can grow without + // copying the underlying array, so give it a challenge. + l := log.With(logger, make([]interface{}, 0, 2)...) + + // Start logging concurrently. Each goroutine logs its id so the logger + // can bucket the event counts. + var wg sync.WaitGroup + wg.Add(goroutines) + const n = 10000 + for i := 0; i < goroutines; i++ { + go func(idx int) { + defer wg.Done() + for j := 0; j < n; j++ { + l.Log("goroutineIdx", idx) + } + }(i) + } + wg.Wait() + + for bucket, have := range counts { + if want := n; want != have { + t.Errorf("bucket %d: want %d, have %d", bucket, want, have) // note Errorf + } + } +} + +func BenchmarkDiscard(b *testing.B) { + logger := log.NewNopLogger() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + logger.Log("k", "v") + } +} + +func BenchmarkOneWith(b *testing.B) { + logger := log.NewNopLogger() + lc := log.With(logger, "k", "v") + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + lc.Log("k", "v") + } +} + +func BenchmarkTwoWith(b *testing.B) { + logger := log.NewNopLogger() + lc := log.With(logger, "k", "v") + for i := 1; i < 2; i++ { + lc = log.With(lc, "k", "v") + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + lc.Log("k", "v") + } +} + +func BenchmarkTenWith(b *testing.B) { + logger := log.NewNopLogger() + lc := log.With(logger, "k", "v") + for i := 1; i < 10; i++ { + lc = log.With(lc, "k", "v") + } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + lc.Log("k", "v") + } +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger.go new file mode 100644 index 000000000..a00305298 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger.go @@ -0,0 +1,62 @@ +package log + +import ( + "bytes" + "io" + "sync" + + "github.com/go-logfmt/logfmt" +) + +type logfmtEncoder struct { + *logfmt.Encoder + buf bytes.Buffer +} + +func (l *logfmtEncoder) Reset() { + l.Encoder.Reset() + l.buf.Reset() +} + +var logfmtEncoderPool = sync.Pool{ + New: func() interface{} { + var enc logfmtEncoder + enc.Encoder = logfmt.NewEncoder(&enc.buf) + return &enc + }, +} + +type logfmtLogger struct { + w io.Writer +} + +// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in +// logfmt format. Each log event produces no more than one call to w.Write. +// The passed Writer must be safe for concurrent use by multiple goroutines if +// the returned Logger will be used concurrently. +func NewLogfmtLogger(w io.Writer) Logger { + return &logfmtLogger{w} +} + +func (l logfmtLogger) Log(keyvals ...interface{}) error { + enc := logfmtEncoderPool.Get().(*logfmtEncoder) + enc.Reset() + defer logfmtEncoderPool.Put(enc) + + if err := enc.EncodeKeyvals(keyvals...); err != nil { + return err + } + + // Add newline to the end of the buffer + if err := enc.EndRecord(); err != nil { + return err + } + + // The Logger interface requires implementations to be safe for concurrent + // use by multiple goroutines. For this implementation that means making + // only one call to l.w.Write() for each call to Log. + if _, err := l.w.Write(enc.buf.Bytes()); err != nil { + return err + } + return nil +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger_test.go new file mode 100644 index 000000000..91bbca15c --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/logfmt_logger_test.go @@ -0,0 +1,57 @@ +package log_test + +import ( + "bytes" + "errors" + "io/ioutil" + "testing" + + "github.com/go-kit/kit/log" + "github.com/go-logfmt/logfmt" +) + +func TestLogfmtLogger(t *testing.T) { + t.Parallel() + buf := &bytes.Buffer{} + logger := log.NewLogfmtLogger(buf) + + if err := logger.Log("hello", "world"); err != nil { + t.Fatal(err) + } + if want, have := "hello=world\n", buf.String(); want != have { + t.Errorf("want %#v, have %#v", want, have) + } + + buf.Reset() + if err := logger.Log("a", 1, "err", errors.New("error")); err != nil { + t.Fatal(err) + } + if want, have := "a=1 err=error\n", buf.String(); want != have { + t.Errorf("want %#v, have %#v", want, have) + } + + buf.Reset() + if err := logger.Log("std_map", map[int]int{1: 2}, "my_map", mymap{0: 0}); err != nil { + t.Fatal(err) + } + if want, have := "std_map=\""+logfmt.ErrUnsupportedValueType.Error()+"\" my_map=special_behavior\n", buf.String(); want != have { + t.Errorf("want %#v, have %#v", want, have) + } +} + +func BenchmarkLogfmtLoggerSimple(b *testing.B) { + benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), baseMessage) +} + +func BenchmarkLogfmtLoggerContextual(b *testing.B) { + benchmarkRunner(b, log.NewLogfmtLogger(ioutil.Discard), withMessage) +} + +func TestLogfmtLoggerConcurrency(t *testing.T) { + t.Parallel() + testConcurrency(t, log.NewLogfmtLogger(ioutil.Discard), 10000) +} + +type mymap map[int]int + +func (m mymap) String() string { return "special_behavior" } diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger.go new file mode 100644 index 000000000..1047d626c --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger.go @@ -0,0 +1,8 @@ +package log + +type nopLogger struct{} + +// NewNopLogger returns a logger that doesn't do anything. +func NewNopLogger() Logger { return nopLogger{} } + +func (nopLogger) Log(...interface{}) error { return nil } diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger_test.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger_test.go new file mode 100644 index 000000000..908ddd816 --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/nop_logger_test.go @@ -0,0 +1,26 @@ +package log_test + +import ( + "testing" + + "github.com/go-kit/kit/log" +) + +func TestNopLogger(t *testing.T) { + t.Parallel() + logger := log.NewNopLogger() + if err := logger.Log("abc", 123); err != nil { + t.Error(err) + } + if err := log.With(logger, "def", "ghi").Log(); err != nil { + t.Error(err) + } +} + +func BenchmarkNopLoggerSimple(b *testing.B) { + benchmarkRunner(b, log.NewNopLogger(), baseMessage) +} + +func BenchmarkNopLoggerContextual(b *testing.B) { + benchmarkRunner(b, log.NewNopLogger(), withMessage) +} diff --git a/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/stdlib.go b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/stdlib.go new file mode 100644 index 000000000..ff96b5dee --- /dev/null +++ b/examples/servers/reading-list/vendor/github.com/go-kit/kit/log/stdlib.go @@ -0,0 +1,116 @@ +package log + +import ( + "io" + "log" + "regexp" + "strings" +) + +// StdlibWriter implements io.Writer by invoking the stdlib log.Print. It's +// designed to be passed to a Go kit logger as the writer, for cases where +// it's necessary to redirect all Go kit log output to the stdlib logger. +// +// If you have any choice in the matter, you shouldn't use this. Prefer to +// redirect the stdlib log to the Go kit logger via NewStdlibAdapter. +type StdlibWriter struct{} + +// Write implements io.Writer. +func (w StdlibWriter) Write(p []byte) (int, error) { + log.Print(strings.TrimSpace(string(p))) + return len(p), nil +} + +// StdlibAdapter wraps a Logger and allows it to be passed to the stdlib +// logger's SetOutput. It will extract date/timestamps, filenames, and +// messages, and place them under relevant keys. +type StdlibAdapter struct { + Logger + timestampKey string + fileKey string + messageKey string +} + +// StdlibAdapterOption sets a parameter for the StdlibAdapter. +type StdlibAdapterOption func(*StdlibAdapter) + +// TimestampKey sets the key for the timestamp field. By default, it's "ts". +func TimestampKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.timestampKey = key } +} + +// FileKey sets the key for the file and line field. By default, it's "caller". +func FileKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.fileKey = key } +} + +// MessageKey sets the key for the actual log message. By default, it's "msg". +func MessageKey(key string) StdlibAdapterOption { + return func(a *StdlibAdapter) { a.messageKey = key } +} + +// NewStdlibAdapter returns a new StdlibAdapter wrapper around the passed +// logger. It's designed to be passed to log.SetOutput. +func NewStdlibAdapter(logger Logger, options ...StdlibAdapterOption) io.Writer { + a := StdlibAdapter{ + Logger: logger, + timestampKey: "ts", + fileKey: "caller", + messageKey: "msg", + } + for _, option := range options { + option(&a) + } + return a +} + +func (a StdlibAdapter) Write(p []byte) (int, error) { + result := subexps(p) + keyvals := []interface{}{} + var timestamp string + if date, ok := result["date"]; ok && date != "" { + timestamp = date + } + if time, ok := result["time"]; ok && time != "" { + if timestamp != "" { + timestamp += " " + } + timestamp += time + } + if timestamp != "" { + keyvals = append(keyvals, a.timestampKey, timestamp) + } + if file, ok := result["file"]; ok && file != "" { + keyvals = append(keyvals, a.fileKey, file) + } + if msg, ok := result["msg"]; ok { + keyvals = append(keyvals, a.messageKey, msg) + } + if err := a.Logger.Log(keyvals...); err != nil { + return 0, err + } + return len(p), nil +} + +const ( + logRegexpDate = `(?P[0-9]{4}/[0-9]{2}/[0-9]{2})?[ ]?` + logRegexpTime = `(?P