Skip to content

Commit 10c5034

Browse files
committed
feat:Add Lemlist Metadata Connector
1 parent 490f2f8 commit 10c5034

File tree

5 files changed

+241
-0
lines changed

5 files changed

+241
-0
lines changed

providers/lemlist/connnector.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package lemlist
2+
3+
import (
4+
"github.com/amp-labs/connectors/common"
5+
"github.com/amp-labs/connectors/internal/components"
6+
"github.com/amp-labs/connectors/internal/components/operations"
7+
"github.com/amp-labs/connectors/internal/components/schema"
8+
"github.com/amp-labs/connectors/providers"
9+
)
10+
11+
const (
12+
restAPIPrefix = "api"
13+
object = "object"
14+
list = "list"
15+
)
16+
17+
type Connector struct {
18+
// Basic connector
19+
*components.Connector
20+
21+
// Require authenticated client
22+
common.RequireAuthenticatedClient
23+
24+
// Supported operations
25+
components.SchemaProvider
26+
}
27+
28+
func NewConnector(params common.Parameters) (*Connector, error) {
29+
return components.Initialize(providers.Lemlist, params, constructor)
30+
}
31+
32+
func constructor(base *components.Connector) (*Connector, error) {
33+
connector := &Connector{Connector: base}
34+
35+
// Set the metadata provider for the connector
36+
connector.SchemaProvider = schema.NewObjectSchemaProvider(
37+
connector.HTTPClient().Client,
38+
schema.FetchModeParallel,
39+
operations.SingleObjectMetadataHandlers{
40+
BuildRequest: connector.buildSingleObjectMetadataRequest,
41+
ParseResponse: connector.parseSingleObjectMetadataResponse,
42+
},
43+
)
44+
45+
return connector, nil
46+
}

providers/lemlist/handlers.go

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package lemlist
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/amp-labs/connectors/common"
9+
"github.com/amp-labs/connectors/common/naming"
10+
"github.com/amp-labs/connectors/common/urlbuilder"
11+
)
12+
13+
func (c *Connector) buildSingleObjectMetadataRequest(ctx context.Context, objectName string) (*http.Request, error) {
14+
url, err := urlbuilder.New(c.ProviderInfo().BaseURL, restAPIPrefix, objectName)
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
url.WithQueryParam("version", "v2")
20+
21+
return http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
22+
}
23+
24+
func (c *Connector) parseSingleObjectMetadataResponse(
25+
ctx context.Context,
26+
objectName string,
27+
request *http.Request,
28+
response *common.JSONHTTPResponse,
29+
) (*common.ObjectMetadata, error) {
30+
var (
31+
firstRecord map[string]any
32+
err error
33+
)
34+
35+
objectMetadata := common.ObjectMetadata{
36+
FieldsMap: make(map[string]string),
37+
DisplayName: naming.CapitalizeFirstLetterEveryWord(objectName),
38+
}
39+
40+
schema, fld := responseSchema(objectName)
41+
42+
switch schema {
43+
case object:
44+
firstRecord, err = parseObject(response, fld)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
case list:
50+
firstRecord, err = parseList(response)
51+
if err != nil {
52+
return nil, err
53+
}
54+
}
55+
56+
for fld := range firstRecord {
57+
objectMetadata.FieldsMap[fld] = fld
58+
}
59+
60+
return &objectMetadata, nil
61+
}
62+
63+
func parseObject(response *common.JSONHTTPResponse, fld string) (map[string]any, error) {
64+
// We're unmarshaling the data to map[string]any,
65+
// all supported objects returns this data type.
66+
data, err := common.UnmarshalJSON[map[string]any](response)
67+
if err != nil {
68+
return nil, common.ErrFailedToUnmarshalBody
69+
}
70+
71+
if len(*data) == 0 {
72+
return nil, common.ErrMissingExpectedValues
73+
}
74+
75+
firstRecord := *data
76+
77+
if fld != "" {
78+
// If this is the case, we're expecting the data in a certain field
79+
// in this current map.
80+
records, okay := (*data)[fld].([]any)
81+
if !okay {
82+
return nil, fmt.Errorf("couldn't convert the data response field data to an array: %w", common.ErrMissingExpectedValues) // nolint:lll
83+
}
84+
85+
if len(records) == 0 {
86+
return nil, fmt.Errorf("%w: could not find a record to sample fields from", common.ErrMissingExpectedValues)
87+
}
88+
89+
// Iterate over the first record.
90+
firstRecord, okay = records[0].(map[string]any)
91+
if !okay {
92+
return nil, fmt.Errorf("couldn't convert the first record data to a map: %w", common.ErrMissingExpectedValues)
93+
}
94+
}
95+
96+
return firstRecord, nil
97+
}
98+
99+
func parseList(response *common.JSONHTTPResponse) (map[string]any, error) {
100+
// We're unmarshaling the data to []map[string]any,
101+
// all supported objects returns this data type.
102+
data, err := common.UnmarshalJSON[[]map[string]any](response)
103+
if err != nil {
104+
return nil, common.ErrFailedToUnmarshalBody
105+
}
106+
107+
if len(*data) == 0 {
108+
return nil, common.ErrMissingExpectedValues
109+
}
110+
111+
return (*data)[0], nil
112+
}

providers/lemlist/supports.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package lemlist
2+
3+
func responseSchema(objectName string) (string, string) {
4+
// team --> an object
5+
// api/team/senders --> an array of objects
6+
// api/team/credits -->object
7+
// api/campaigns --> object campaigns array
8+
// api/activities --> an array of objects
9+
// api/unsubscirbes --> an array of objects
10+
// api/hooks --> an array of objects
11+
// api/database/filters -->array of objects
12+
// api/schema/people --> an object
13+
// api/schema/companies --> an object
14+
switch objectName {
15+
case "campaigns", "schedules":
16+
return object, objectName
17+
case "team/senders", "activities", "unsubscirbes", "hooks", "database/filters":
18+
return list, ""
19+
default:
20+
return object, ""
21+
}
22+
}

test/lemlist/connector.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package lemlist
2+
3+
import (
4+
"context"
5+
6+
"github.com/amp-labs/connectors/common"
7+
"github.com/amp-labs/connectors/common/scanning/credscanning"
8+
"github.com/amp-labs/connectors/providers"
9+
"github.com/amp-labs/connectors/providers/lemlist"
10+
"github.com/amp-labs/connectors/test/utils"
11+
)
12+
13+
func GetLemlistConnector(ctx context.Context) *lemlist.Connector {
14+
filePath := credscanning.LoadPath(providers.Lemlist)
15+
reader := utils.MustCreateProvCredJSON(filePath, false, false)
16+
17+
client, err := common.NewApiKeyQueryParamAuthHTTPClient(ctx, "access_token", reader.Get(credscanning.Fields.ApiKey))
18+
if err != nil {
19+
utils.Fail("error creating client", "error", err)
20+
}
21+
22+
conn, err := lemlist.NewConnector(
23+
common.Parameters{AuthenticatedClient: client},
24+
)
25+
if err != nil {
26+
utils.Fail("error creating connector", "error", err)
27+
}
28+
29+
return conn
30+
}

test/lemlist/metadata/main.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/amp-labs/connectors/test/lemlist"
8+
"github.com/amp-labs/connectors/test/utils"
9+
)
10+
11+
func main() {
12+
if err := run(); err != nil {
13+
utils.Fail(err.Error())
14+
}
15+
}
16+
17+
func run() error {
18+
ctx := context.Background()
19+
connector := lemlist.GetLemlistConnector(ctx)
20+
21+
m, err := connector.ListObjectMetadata(ctx, []string{"campaigns", "team", "schedules", "schema/people"})
22+
if err != nil {
23+
return err
24+
}
25+
26+
// Print the results
27+
fmt.Println("Results: ", m.Result)
28+
fmt.Println("Errors: ", m.Errors)
29+
30+
return nil
31+
}

0 commit comments

Comments
 (0)