github.com/thiagoyeds/go-cloud@v0.26.0/docstore/awsdynamodb/urls.go (about)

     1  // Copyright 2019 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package awsdynamodb
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"net/url"
    22  	"sync"
    23  
    24  	"github.com/aws/aws-sdk-go/aws/client"
    25  	dyn "github.com/aws/aws-sdk-go/service/dynamodb"
    26  	gcaws "gocloud.dev/aws"
    27  	"gocloud.dev/docstore"
    28  )
    29  
    30  func init() {
    31  	docstore.DefaultURLMux().RegisterCollection(Scheme, new(lazySessionOpener))
    32  }
    33  
    34  type lazySessionOpener struct {
    35  	init   sync.Once
    36  	opener *URLOpener
    37  	err    error
    38  }
    39  
    40  func (o *lazySessionOpener) OpenCollectionURL(ctx context.Context, u *url.URL) (*docstore.Collection, error) {
    41  	o.init.Do(func() {
    42  		sess, err := gcaws.NewDefaultSession()
    43  		if err != nil {
    44  			o.err = err
    45  			return
    46  		}
    47  		o.opener = &URLOpener{
    48  			ConfigProvider: sess,
    49  		}
    50  	})
    51  	if o.err != nil {
    52  		return nil, fmt.Errorf("open collection %s: %v", u, o.err)
    53  	}
    54  	return o.opener.OpenCollectionURL(ctx, u)
    55  }
    56  
    57  // Scheme is the URL scheme dynamodb registers its URLOpener under on
    58  // docstore.DefaultMux.
    59  const Scheme = "dynamodb"
    60  
    61  // URLOpener opens dynamodb URLs like
    62  // "dynamodb://mytable?partition_key=partkey&sort_key=sortkey".
    63  //
    64  // The URL Host is used as the table name. See
    65  // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html
    66  // for more details.
    67  //
    68  // The following query parameters are supported:
    69  //
    70  //   - partition_key (required): the path to the partition key of a table or an index.
    71  //   - sort_key: the path to the sort key of a table or an index.
    72  //   - allow_scans: if "true", allow table scans to be used for queries
    73  //   - consistent_read: if "true", a strongly consistent read is used whenever possible.
    74  //
    75  // See https://godoc.org/gocloud.dev/aws#ConfigFromURLParams for supported query
    76  // parameters for overriding the aws.Session from the URL.
    77  type URLOpener struct {
    78  	// ConfigProvider must be set to a non-nil value.
    79  	ConfigProvider client.ConfigProvider
    80  }
    81  
    82  // OpenCollectionURL opens the collection at the URL's path. See the package doc for more details.
    83  func (o *URLOpener) OpenCollectionURL(_ context.Context, u *url.URL) (*docstore.Collection, error) {
    84  	db, tableName, partitionKey, sortKey, opts, err := o.processURL(u)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return OpenCollection(db, tableName, partitionKey, sortKey, opts)
    89  }
    90  
    91  func (o *URLOpener) processURL(u *url.URL) (db *dyn.DynamoDB, tableName, partitionKey, sortKey string, opts *Options, err error) {
    92  	q := u.Query()
    93  
    94  	partitionKey = q.Get("partition_key")
    95  	if partitionKey == "" {
    96  		return nil, "", "", "", nil, fmt.Errorf("open collection %s: partition_key is required to open a table", u)
    97  	}
    98  	q.Del("partition_key")
    99  	sortKey = q.Get("sort_key")
   100  	q.Del("sort_key")
   101  	opts = &Options{
   102  		AllowScans:     q.Get("allow_scans") == "true",
   103  		RevisionField:  q.Get("revision_field"),
   104  		ConsistentRead: q.Get("consistent_read") == "true",
   105  	}
   106  	q.Del("allow_scans")
   107  	q.Del("revision_field")
   108  	q.Del("consistent_read")
   109  
   110  	tableName = u.Host
   111  	if tableName == "" {
   112  		return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL's host cannot be empty (the table name)", u)
   113  	}
   114  	if u.Path != "" {
   115  		return nil, "", "", "", nil, fmt.Errorf("open collection %s: URL path must be empty, only the host is needed", u)
   116  	}
   117  
   118  	configProvider := &gcaws.ConfigOverrider{
   119  		Base: o.ConfigProvider,
   120  	}
   121  	overrideCfg, err := gcaws.ConfigFromURLParams(q)
   122  	if err != nil {
   123  		return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err)
   124  	}
   125  	configProvider.Configs = append(configProvider.Configs, overrideCfg)
   126  	db, err = Dial(configProvider)
   127  	if err != nil {
   128  		return nil, "", "", "", nil, fmt.Errorf("open collection %s: %v", u, err)
   129  	}
   130  	return db, tableName, partitionKey, sortKey, opts, nil
   131  }
   132  
   133  // Dial gets an AWS DynamoDB service client.
   134  func Dial(p client.ConfigProvider) (*dyn.DynamoDB, error) {
   135  	if p == nil {
   136  		return nil, errors.New("getting Dynamo service: no AWS session provided")
   137  	}
   138  	return dyn.New(p), nil
   139  }