diff --git a/CHANGELOG.md b/CHANGELOG.md index afa13ec..37a28f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Master +* [#49](https://github.com/contentful-labs/contentful.ex/pull/49) Adds extended query syntax for building more complex queries, as suggested by @ryansch in [#38](https://github.com/contentful-labs/contentful.ex/issues/38) +* adds testgin against Elixir 1.10.4 + ## 0.3.2 * [#47](https://github.com/contentful-labs/contentful.ex/pull/47) Handle bitstring case when resolving assets (thanks @aspala) diff --git a/fixture/vcr_cassettes/multiple_assets,_filtered_by_name,_negated.json b/fixture/vcr_cassettes/multiple_assets,_filtered_by_name,_negated.json new file mode 100644 index 0000000..5619788 --- /dev/null +++ b/fixture/vcr_cassettes/multiple_assets,_filtered_by_name,_negated.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": { + "authorization": "***", + "User-Agent": "Contentful Elixir SDK", + "accept": "application/json" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://cdn.contentful.com/spaces/bmehzfuz4raf/environments/master/assets?fields.title%5Bne%5D=bafoo" + }, + "response": { + "binary": false, + "body": "{\n \"sys\": {\n \"type\": \"Array\"\n },\n \"total\": 1,\n \"skip\": 0,\n \"limit\": 100,\n \"items\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"5ECf6ltDUOnX441PtBR8Wk\",\n \"type\": \"Asset\",\n \"createdAt\": \"2020-03-16T10:06:11.604Z\",\n \"updatedAt\": \"2020-06-10T12:59:00.879Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 3,\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"file\": {\n \"url\": \"//images.ctfassets.net/bmehzfuz4raf/5ECf6ltDUOnX441PtBR8Wk/fa3dc5cde2b4d4c5736566c956f20163/Screenshot_from_2020-03-11_22-56-44.png\",\n \"details\": {\n \"size\": 247990,\n \"image\": {\n \"width\": 1920,\n \"height\": 1053\n }\n },\n \"fileName\": \"Screenshot from 2020-03-11 22-56-44.png\",\n \"contentType\": \"image/png\"\n }\n }\n }\n ]\n}\n", + "headers": { + "Connection": "keep-alive", + "Content-Length": "1131", + "Access-Control-Allow-Headers": "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature", + "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Etag", + "Access-Control-Max-Age": "86400", + "CF-Environment-Id": "master", + "CF-Environment-Uuid": "69b0fe47-9d71-4e0c-a7e0-4e12cb573930", + "CF-Organization-Id": "0bEbsBel9WL3OIaiqx0MnO", + "CF-Space-Id": "bmehzfuz4raf", + "Content-Type": "application/vnd.contentful.delivery.v1+json", + "Contentful-Api": "cda_cached", + "ETag": "W/\"6071154412135978374\"", + "Server": "Contentful", + "X-Content-Type-Options": "nosniff", + "X-Contentful-Region": "us-east-1", + "Accept-Ranges": "bytes", + "Date": "Sun, 05 Jul 2020 14:18:56 GMT", + "Via": "1.1 varnish", + "Age": "0", + "X-Served-By": "cache-hhn4034-HHN", + "X-Cache": "MISS", + "X-Cache-Hits": "0", + "Vary": "Accept-Encoding", + "x-contentful-request-id": "591a465a-dc43-4014-a5bb-dca6d905fcdb" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/multiple_assets,_filtered_by_name.json b/fixture/vcr_cassettes/multiple_assets,_filtered_by_name.json new file mode 100644 index 0000000..39bb9c7 --- /dev/null +++ b/fixture/vcr_cassettes/multiple_assets,_filtered_by_name.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": { + "authorization": "***", + "User-Agent": "Contentful Elixir SDK", + "accept": "application/json" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://cdn.contentful.com/spaces/bmehzfuz4raf/environments/master/assets?fields.title=bafoo" + }, + "response": { + "binary": false, + "body": "{\n \"sys\": {\n \"type\": \"Array\"\n },\n \"total\": 1,\n \"skip\": 0,\n \"limit\": 100,\n \"items\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"577fpmbIfYD71VCjCpYA84\",\n \"type\": \"Asset\",\n \"createdAt\": \"2020-03-16T10:30:45.706Z\",\n \"updatedAt\": \"2020-03-16T10:30:45.706Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 1,\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"title\": \"bafoo\",\n \"description\": \"a pdf\",\n \"file\": {\n \"url\": \"//assets.ctfassets.net/bmehzfuz4raf/577fpmbIfYD71VCjCpYA84/6456d4157900af3b35537f5f15dd97c2/Travis_Picking_-_The_LEGENDARY_picking_pattern.pdf\",\n \"details\": {\n \"size\": 90783\n },\n \"fileName\": \"Travis Picking - The LEGENDARY picking pattern.pdf\",\n \"contentType\": \"application/pdf\"\n }\n }\n }\n ]\n}\n", + "headers": { + "Connection": "keep-alive", + "Content-Length": "1120", + "Access-Control-Allow-Headers": "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature", + "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Etag", + "Access-Control-Max-Age": "86400", + "CF-Environment-Id": "master", + "CF-Environment-Uuid": "69b0fe47-9d71-4e0c-a7e0-4e12cb573930", + "CF-Organization-Id": "0bEbsBel9WL3OIaiqx0MnO", + "CF-Space-Id": "bmehzfuz4raf", + "Content-Type": "application/vnd.contentful.delivery.v1+json", + "Contentful-Api": "cda_cached", + "ETag": "W/\"14856425138171294666\"", + "Server": "Contentful", + "X-Content-Type-Options": "nosniff", + "X-Contentful-Region": "us-east-1", + "Accept-Ranges": "bytes", + "Date": "Sun, 05 Jul 2020 14:17:51 GMT", + "Via": "1.1 varnish", + "Age": "0", + "X-Served-By": "cache-fra19140-FRA", + "X-Cache": "MISS", + "X-Cache-Hits": "0", + "Vary": "Accept-Encoding", + "x-contentful-request-id": "f2ae57c3-a489-4c9a-a31a-6dbaa55c3d06" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/multiple_entries,_filtered_by_content_type.json b/fixture/vcr_cassettes/multiple_entries,_filtered_by_content_type.json new file mode 100644 index 0000000..f241bce --- /dev/null +++ b/fixture/vcr_cassettes/multiple_entries,_filtered_by_content_type.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": { + "authorization": "***", + "User-Agent": "Contentful Elixir SDK", + "accept": "application/json" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://cdn.contentful.com/spaces/bmehzfuz4raf/environments/master/entries?content_type=category" + }, + "response": { + "binary": false, + "body": "{\n \"sys\": {\n \"type\": \"Array\"\n },\n \"total\": 2,\n \"skip\": 0,\n \"limit\": 100,\n \"items\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"7qCGg4LadgJUcx5cr35Ou9\",\n \"type\": \"Entry\",\n \"createdAt\": \"2020-07-05T09:44:36.704Z\",\n \"updatedAt\": \"2020-07-05T09:44:36.704Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 1,\n \"contentType\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"ContentType\",\n \"id\": \"category\"\n }\n },\n \"locale\": \"en-US\"\n },\n \"fields\": {}\n },\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"4RPjazUzQMqemyNlcD3b9i\",\n \"type\": \"Entry\",\n \"createdAt\": \"2020-03-17T15:22:17.537Z\",\n \"updatedAt\": \"2020-04-30T08:18:45.388Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 2,\n \"contentType\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"ContentType\",\n \"id\": \"category\"\n }\n },\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"name\": \"A standard category\",\n \"image\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Asset\",\n \"id\": \"5ECf6ltDUOnX441PtBR8Wk\"\n }\n }\n }\n }\n ],\n \"includes\": {\n \"Asset\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"5ECf6ltDUOnX441PtBR8Wk\",\n \"type\": \"Asset\",\n \"createdAt\": \"2020-03-16T10:06:11.604Z\",\n \"updatedAt\": \"2020-06-10T12:59:00.879Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 3,\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"file\": {\n \"url\": \"//images.ctfassets.net/bmehzfuz4raf/5ECf6ltDUOnX441PtBR8Wk/fa3dc5cde2b4d4c5736566c956f20163/Screenshot_from_2020-03-11_22-56-44.png\",\n \"details\": {\n \"size\": 247990,\n \"image\": {\n \"width\": 1920,\n \"height\": 1053\n }\n },\n \"fileName\": \"Screenshot from 2020-03-11 22-56-44.png\",\n \"contentType\": \"image/png\"\n }\n }\n }\n ]\n }\n}\n", + "headers": { + "Connection": "keep-alive", + "Content-Length": "2952", + "Access-Control-Allow-Headers": "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature", + "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Etag", + "Access-Control-Max-Age": "86400", + "CF-Environment-Id": "master", + "CF-Environment-Uuid": "69b0fe47-9d71-4e0c-a7e0-4e12cb573930", + "CF-Organization-Id": "0bEbsBel9WL3OIaiqx0MnO", + "CF-Space-Id": "bmehzfuz4raf", + "Content-Type": "application/vnd.contentful.delivery.v1+json", + "Contentful-Api": "cda_cached", + "ETag": "W/\"17446375927234312903\"", + "Server": "Contentful", + "X-Content-Type-Options": "nosniff", + "X-Contentful-Region": "us-east-1", + "Accept-Ranges": "bytes", + "Date": "Sun, 05 Jul 2020 10:15:36 GMT", + "Via": "1.1 varnish", + "Age": "1137", + "X-Served-By": "cache-hhn4067-HHN", + "X-Cache": "HIT", + "X-Cache-Hits": "2", + "Vary": "Accept-Encoding", + "x-contentful-request-id": "f23efb99-72c1-4342-8dcb-991550eee851" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/single_entry_with_select_filters.json b/fixture/vcr_cassettes/single_entry_with_select_filters.json new file mode 100644 index 0000000..ebc97a4 --- /dev/null +++ b/fixture/vcr_cassettes/single_entry_with_select_filters.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": { + "authorization": "***", + "User-Agent": "Contentful Elixir SDK", + "accept": "application/json" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://cdn.contentful.com/spaces/bmehzfuz4raf/environments/master/entries?content_type=category&sys.id=7qCGg4LadgJUcx5cr35Ou9" + }, + "response": { + "binary": false, + "body": "{\n \"sys\": {\n \"type\": \"Array\"\n },\n \"total\": 1,\n \"skip\": 0,\n \"limit\": 100,\n \"items\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"7qCGg4LadgJUcx5cr35Ou9\",\n \"type\": \"Entry\",\n \"createdAt\": \"2020-07-05T09:44:36.704Z\",\n \"updatedAt\": \"2020-07-05T09:44:36.704Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 1,\n \"contentType\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"ContentType\",\n \"id\": \"category\"\n }\n },\n \"locale\": \"en-US\"\n },\n \"fields\": {}\n }\n ]\n}\n", + "headers": { + "Connection": "keep-alive", + "Content-Length": "846", + "Access-Control-Allow-Headers": "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature", + "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Etag", + "Access-Control-Max-Age": "86400", + "CF-Environment-Id": "master", + "CF-Environment-Uuid": "69b0fe47-9d71-4e0c-a7e0-4e12cb573930", + "CF-Organization-Id": "0bEbsBel9WL3OIaiqx0MnO", + "CF-Space-Id": "bmehzfuz4raf", + "Content-Type": "application/vnd.contentful.delivery.v1+json", + "Contentful-Api": "cda_cached", + "ETag": "\"5660330800706425331\"", + "Server": "Contentful", + "X-Content-Type-Options": "nosniff", + "X-Contentful-Region": "us-east-1", + "Accept-Ranges": "bytes", + "Date": "Sun, 05 Jul 2020 10:49:58 GMT", + "Via": "1.1 varnish", + "Age": "0", + "X-Served-By": "cache-fra19145-FRA", + "X-Cache": "MISS", + "X-Cache-Hits": "0", + "Vary": "Accept-Encoding", + "x-contentful-request-id": "3d69ef80-daf0-4be8-bbf5-9bf0b0f7929e" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/fixture/vcr_cassettes/some_entries_not_having_an_id.json b/fixture/vcr_cassettes/some_entries_not_having_an_id.json new file mode 100644 index 0000000..3cc995c --- /dev/null +++ b/fixture/vcr_cassettes/some_entries_not_having_an_id.json @@ -0,0 +1,50 @@ +[ + { + "request": { + "body": "", + "headers": { + "authorization": "***", + "User-Agent": "Contentful Elixir SDK", + "accept": "application/json" + }, + "method": "get", + "options": [], + "request_body": "", + "url": "https://cdn.contentful.com/spaces/bmehzfuz4raf/environments/master/entries?content_type=category&sys.id%5Bne%5D=7qCGg4LadgJUcx5cr35Ou9" + }, + "response": { + "binary": false, + "body": "{\n \"sys\": {\n \"type\": \"Array\"\n },\n \"total\": 1,\n \"skip\": 0,\n \"limit\": 100,\n \"items\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"4RPjazUzQMqemyNlcD3b9i\",\n \"type\": \"Entry\",\n \"createdAt\": \"2020-03-17T15:22:17.537Z\",\n \"updatedAt\": \"2020-04-30T08:18:45.388Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 2,\n \"contentType\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"ContentType\",\n \"id\": \"category\"\n }\n },\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"name\": \"A standard category\",\n \"image\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Asset\",\n \"id\": \"5ECf6ltDUOnX441PtBR8Wk\"\n }\n }\n }\n }\n ],\n \"includes\": {\n \"Asset\": [\n {\n \"sys\": {\n \"space\": {\n \"sys\": {\n \"type\": \"Link\",\n \"linkType\": \"Space\",\n \"id\": \"bmehzfuz4raf\"\n }\n },\n \"id\": \"5ECf6ltDUOnX441PtBR8Wk\",\n \"type\": \"Asset\",\n \"createdAt\": \"2020-03-16T10:06:11.604Z\",\n \"updatedAt\": \"2020-06-10T12:59:00.879Z\",\n \"environment\": {\n \"sys\": {\n \"id\": \"master\",\n \"type\": \"Link\",\n \"linkType\": \"Environment\"\n }\n },\n \"revision\": 3,\n \"locale\": \"en-US\"\n },\n \"fields\": {\n \"file\": {\n \"url\": \"//images.ctfassets.net/bmehzfuz4raf/5ECf6ltDUOnX441PtBR8Wk/fa3dc5cde2b4d4c5736566c956f20163/Screenshot_from_2020-03-11_22-56-44.png\",\n \"details\": {\n \"size\": 247990,\n \"image\": {\n \"width\": 1920,\n \"height\": 1053\n }\n },\n \"fileName\": \"Screenshot from 2020-03-11 22-56-44.png\",\n \"contentType\": \"image/png\"\n }\n }\n }\n ]\n }\n}\n", + "headers": { + "Connection": "keep-alive", + "Content-Length": "2205", + "Access-Control-Allow-Headers": "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation,X-Contentful-User-Agent,X-Contentful-Enable-Alpha-Feature", + "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Etag", + "Access-Control-Max-Age": "86400", + "CF-Environment-Id": "master", + "CF-Environment-Uuid": "69b0fe47-9d71-4e0c-a7e0-4e12cb573930", + "CF-Organization-Id": "0bEbsBel9WL3OIaiqx0MnO", + "CF-Space-Id": "bmehzfuz4raf", + "Content-Type": "application/vnd.contentful.delivery.v1+json", + "Contentful-Api": "cda_cached", + "ETag": "W/\"144981087548954756\"", + "Server": "Contentful", + "X-Content-Type-Options": "nosniff", + "X-Contentful-Region": "us-east-1", + "Accept-Ranges": "bytes", + "Date": "Sun, 05 Jul 2020 11:44:23 GMT", + "Via": "1.1 varnish", + "Age": "0", + "X-Served-By": "cache-fra19143-FRA", + "X-Cache": "MISS", + "X-Cache-Hits": "0", + "Vary": "Accept-Encoding", + "x-contentful-request-id": "4872994b-841a-4870-b38e-f97140ec1281" + }, + "status_code": 200, + "type": "ok" + } + } +] \ No newline at end of file diff --git a/lib/contentful/configuration.ex b/lib/contentful/configuration.ex new file mode 100644 index 0000000..7ab4ed0 --- /dev/null +++ b/lib/contentful/configuration.ex @@ -0,0 +1,14 @@ +defmodule Contentful.Configuration do + @moduledoc """ + Provides Configuration for the different Contentful modules, providing easier access to the values + in config/*.exs + """ + + def get(key, context \\ :delivery) do + context |> config() |> Keyword.get(key) + end + + defp config(context) do + Application.get_env(:contentful, context, []) + end +end diff --git a/lib/contentful/misc.ex b/lib/contentful/misc.ex new file mode 100644 index 0000000..774f152 --- /dev/null +++ b/lib/contentful/misc.ex @@ -0,0 +1,26 @@ +defmodule Contentful.Misc do + @moduledoc """ + Provides some functions that fit nowhere else and can be shared freely. + """ + + @doc """ + provides a generic fallback method in case a returned value is `nil`. + + Inspired by [@expede](https://github.com/expede) and her talk at Code BEAM V 2020. + + Usable in conjunction with other getters to enable function chains: + + ## Example + + 1 = [a: 1, b: 2, c: 3] |> Keyword.get(:a) + 4 = [a: 1, b: 2, c: 3] |> Keyword.get(:d) |> fallback(4) + """ + @spec fallback(nil, any()) :: any() + def fallback(nil, value) do + value + end + + def fallback(value, _) do + value + end +end diff --git a/lib/contentful/query.ex b/lib/contentful/query.ex index 306d994..4551201 100644 --- a/lib/contentful/query.ex +++ b/lib/contentful/query.ex @@ -26,13 +26,18 @@ defmodule Contentful.Query do ``` """ + alias Contentful.Configuration alias Contentful.ContentType alias Contentful.Delivery + alias Contentful.Delivery.Assets alias Contentful.Delivery.Entries alias Contentful.Delivery.Spaces + alias Contentful.Request alias Contentful.Space alias Contentful.SysData + @allowed_filter_modifiers [:all, :in, :nin, :ne, :lte, :gte, :lt, :gt, :match, :exists] + @doc """ adds the `include` parameter to a query. @@ -165,9 +170,9 @@ defmodule Contentful.Query do | {:error, atom(), original_message: String.t()} def fetch_all( queryable, - space \\ Delivery.config(:space_id), - env \\ Delivery.config(:environment), - api_key \\ Delivery.config(:access_token) + space \\ Configuration.get(:space_id), + env \\ Configuration.get(:environment), + api_key \\ Configuration.get(:access_token) ) def fetch_all({Spaces, _}, _, _, _) do @@ -185,13 +190,19 @@ defmodule Contentful.Query do env, api_key ) do - url = [ - space |> Delivery.url(env), - queryable.endpoint(), - parameters |> Delivery.collection_query_params() - ] + params = parameters |> Request.collection_query_params() - {url, api_key |> Delivery.request_headers()} + url = + [ + space |> Delivery.url(env), + queryable.endpoint() + ] + |> Enum.join() + |> URI.parse() + |> add_query_params(params) + |> to_string() + + {url, api_key |> Request.headers()} |> Delivery.send_request() |> Delivery.parse_response(&queryable.resolve_collection_response/1) end @@ -220,9 +231,9 @@ defmodule Contentful.Query do def fetch_one( queryable, id \\ nil, - space \\ Delivery.config(:space_id), - env \\ Delivery.config(:environment), - api_key \\ Delivery.config(:access_token) + space \\ Configuration.get(:space_id), + env \\ Configuration.get(:environment), + api_key \\ Configuration.get(:access_token) ) def fetch_one(queryable, id, %Space{sys: %SysData{id: space_id}}, env, api_key) do @@ -266,11 +277,134 @@ defmodule Contentful.Query do queryable end - {url, api_key |> Delivery.request_headers()} + {url, api_key |> Request.headers()} |> Delivery.send_request() |> Delivery.parse_response(&queryable.resolve_entity_response/1) end + @doc """ + Adds a filter condition to the query. + + This will work for Entries *requiring* a call to `content_type` before: + + ## Example + + import Contentful.Query + alias Contentful.Delivery.Entries + + {:ok, entries, total: 1} + = Entries + |> content_type("dogs") + |> by(name: "Hasso", breed: "dalmatian") + |> fetch_all + + This will also allow for more complex queries using modifiers: + + ## Example + + import Contentful.Query + alias Contentful.Delivery.Entries + + {:ok, entries, total: 100} + = Entries + |> content_type("dogs") + |> by(name: [ne: "Hasso"], breed: "dalmatian") + |> fetch_all + + Allowed modifiers are `[:in, :nin, :ne, :lte, :gte, :lt, :gt, :match, :exist]`. See the + [official docs](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/equality-operator) + for adding search parameters this way. + + Working with `Contentful.Delivery.Assets` requires no `content_type` call: + + ## Example + + import Contentful.Query + alias Contentful.Delivery.Assets + + {:ok, assets, total: 1} = Assets |> by(id: "foobar") |> fetch_all + + Calling `by/2` allows for adding multiple conditions to the query: + + ## Example + + import Contentful.Query + alias Contentful.Delivery.Assets + + {:ok, assets, total: 200} + = Assets + |> by(tags: [nin: "maps"]) + |> fetch_all + + """ + @spec by(tuple(), list()) :: tuple() + def by({Entries, parameters}, new_select_params) do + select_params = parameters |> Keyword.take([:select_params]) + + content_type_present? = parameters |> Keyword.take([:content_type]) |> length() > 0 + + unless content_type_present? do + raise %ArgumentError{ + message: """ + Filtering for entries requires a content_type, example: + + Entries |> content_type("cats") |> by(name: "Gretchen") + """ + } + end + + {Entries, + parameters |> Keyword.put(:select_params, select_params |> Keyword.merge(new_select_params))} + end + + def by({Assets, parameters}, new_select_params) do + select_params = parameters |> Keyword.take([:select_params]) + + {Assets, + parameters |> Keyword.put(:select_params, select_params |> Keyword.merge(new_select_params))} + end + + def by(Entries, select_params) do + by({Entries, []}, select_params) + end + + def by(Assets, select_params) do + by({Assets, []}, select_params) + end + + def by(queryable, _select_params) do + queryable + end + + @doc """ + allows for full text search over all entries fields. The original nomenclature fromthe API docs is `query`. + + This has been renamed for clarity here. + + ## Example + + import Contentful.Query + {Entries, [query: "Nyancat"]} = Entries |> search_full_text("Nyancat") + + # or, with full `fetch_all` + {:ok, nyan_cats, total: 616} = + Entries + |> search_full_text("Nyancat") + |> fetch_all + """ + @spec search_full_text(tuple(), term()) :: tuple() + def search_full_text({Entries, parameters}, term) do + {Entries, parameters |> Keyword.put(:query, term)} + end + + def search_full_text(Entries, term) do + search_full_text({Entries, []}, term) + end + + def search_full_text(queryable, _term) do + queryable + end + @doc """ will __resolve__ a query chain by constructing a `Stream.resource` around a possible API response allowing for lazy evaluation of queries. Cann be helpful with translating collection calls of @@ -290,16 +424,16 @@ defmodule Contentful.Query do # 10 times total. Assets |> limit(100) |> Enum.take(1000) - # will not work with Spaces, though, as they + # will not work with Spaces, though, as they is no collection endpoint """ @spec stream(tuple(), String.t(), String.t(), String.t()) :: Enumerable.t() def stream( queryable, - space \\ Delivery.config(:space_id), - env \\ Delivery.config(:environment), - api_key \\ Delivery.config(:access_token) + space \\ Configuration.get(:space_id), + env \\ Configuration.get(:environment), + api_key \\ Configuration.get(:access_token) ) def stream(Spaces, _space, _env, _api_key) do @@ -309,4 +443,16 @@ defmodule Contentful.Query do def stream(args, space, env, api_key) do Contentful.Stream.stream(args, space, env, api_key) end + + def allowed_filter_modifiers do + @allowed_filter_modifiers + end + + defp add_query_params(uri, []) do + uri + end + + defp add_query_params(%URI{} = uri, params) do + uri |> Map.put(:query, URI.encode_query(params)) + end end diff --git a/lib/contentful/request.ex b/lib/contentful/request.ex new file mode 100644 index 0000000..d9f5f39 --- /dev/null +++ b/lib/contentful/request.ex @@ -0,0 +1,127 @@ +defmodule Contentful.Request do + @moduledoc """ + Encapsulates functions invloved in making a request towards the Contentful APIs. + """ + alias Contentful.Configuration + alias Contentful.Query + + import Contentful.Misc, only: [fallback: 2] + + @agent_name "Contentful Elixir SDK" + + @agent_header [ + "User-Agent": @agent_name, + "X-Contentful-User-Agent": @agent_name + ] + + @accept_header [ + accept: "application/json" + ] + + def collection_query_params([]), do: [] + + @doc """ + parses the options for retrieving a collection, usually triggered by a `Contentful.Query.fetch_all/4`. + It will drop any option that is not in an allowed set of parameter options. + + ## Examples + + [limit: 50, skip: 25, order: "foobar"] + = collection_query_params(limit: 50, baz: "foo", skip: 25, order: "foobar", bar: 42) + + Also provides support for mapping out some of the API specific syntax in field handling. + + ## Examples + [{:"sys.id[ne]", "foobar"}] = collection_query_params(select_params: [id: [ne: "foobar"]]) + + """ + @spec collection_query_params( + limit: pos_integer(), + skip: non_neg_integer(), + include: non_neg_integer(), + content_type: String.t(), + query: String.t(), + select_params: map() + ) :: list() + def collection_query_params(options) do + filters = + options + |> Keyword.get(:select_params) + |> fallback([]) + |> deconstruct_filters() + + options + |> Keyword.take([:limit, :skip, :include, :content_type, :query]) + |> Keyword.merge(filters) + end + + @doc """ + Builds the request headers for a request against the CDA, taking api access tokens into account + + ## Examples + my_access_token = "foobarfoob4z" + [ + "Authorization": "Bearer foobarfoob4z", + "User-Agent": "Contentful Elixir SDK", + "Accept": "application/json" + ] = my_access_token |> headers() + """ + @spec headers(String.t()) :: keyword() + def headers(api_key) do + api_key + |> authorization_header() + |> Keyword.merge(@agent_header) + |> Keyword.merge(@accept_header) + end + + defp authorization_header(nil) do + api_key_from_configuration() |> authorization_header() + end + + defp authorization_header(token) do + [authorization: "Bearer #{token}"] + end + + defp api_key_from_configuration do + Configuration.get(:api_key) |> fallback("___MISSING_API_KEY___") + end + + defp deconstruct_filters(filters) do + filters + |> Enum.map(fn {field, value} = _filter -> + mapped_value = + case field do + :id -> + {:"sys.id", value} + + field_name -> + {:"fields.#{field_name}", value} + end + + case mapped_value do + {field, value} when is_binary(value) -> + {field, value} + + {field, [{modifier, modifier_value}]} when is_list(modifier_value) -> + create_modified_field(field, modifier, Enum.join(modifier_value, ",")) + + {field, [{modifier, modifier_value}]} -> + create_modified_field(field, modifier, modifier_value) + end + end) + end + + defp create_modified_field(field, modifier, field_value) do + unless Query.allowed_filter_modifiers() |> Enum.member?(modifier) do + raise %ArgumentError{ + message: """ + Invalid modifier for field '#{field}'! + + Allowed modifiers are: #{Query.allowed_filter_modifiers() |> Enum.join(", ")} + """ + } + end + + {:"#{field}[#{modifier}]", field_value} + end +end diff --git a/lib/contentful_delivery/delivery.ex b/lib/contentful_delivery/delivery.ex index 5fa01a9..2a02675 100644 --- a/lib/contentful_delivery/delivery.ex +++ b/lib/contentful_delivery/delivery.ex @@ -123,8 +123,10 @@ defmodule Contentful.Delivery do * [Contentful Delivery API docs](https://www.contentful.com/developers/docs/references/content-delivery-api/) (CDA). """ + import Contentful.Misc, only: [fallback: 2] import HTTPoison, only: [get: 2] + alias Contentful.Configuration alias HTTPoison.Response @endpoint "cdn.contentful.com" @@ -132,14 +134,6 @@ defmodule Contentful.Delivery do @protocol "https" @separator "/" - @agent_header [ - "User-Agent": "Contentful Elixir SDK" - ] - - @accept_header [ - accept: "application/json" - ] - @doc """ Gets the json library for the Contentful Delivery API based on the config/config.exs. @@ -217,70 +211,14 @@ defmodule Contentful.Delivery do [space |> url(), "environments", env] |> Enum.join(@separator) end - @doc """ - Builds the request headers for a request against the CDA, taking api access tokens into account - - ## Examples - my_access_token = "foobarfoob4z" - [ - "Authorization": "Bearer foobarfoob4z", - "User-Agent": "Contentful Elixir SDK", - "Accept": "application/json" - ] = my_access_token |> request_headers() - """ - @spec request_headers(String.t()) :: keyword() - def request_headers(api_key) do - api_key - |> authorization_header() - |> Keyword.merge(@agent_header) - |> Keyword.merge(@accept_header) - end - @doc """ Sends a request against the CDA. It's really just a wrapper around `HTTPoison.get/2` """ - @spec send_request(tuple()) :: {:ok, Response.t()} + @spec send_request({binary(), any()}) :: {:error, HTTPoison.Error.t()} | {:ok, Response.t()} def send_request({url, headers}) do get(url, headers) end - @doc """ - Prevents parsing of empty options. - - ## Examples - - "" = collection_query_params([]) - - """ - def collection_query_params([]) do - "" - end - - @doc """ - parses the options for retrieving a collection. It will drop any option that is not in - @collection_filters ([:limit, :skip]) - - ## Examples - - "?limit=50&skip=25&order=foobar" - = collection_query_params(limit: 50, baz: "foo", skip: 25, order: "foobar", bar: 42) - - """ - @spec collection_query_params( - limit: pos_integer(), - skip: non_neg_integer(), - content_type: String.t(), - include: non_neg_integer() - ) :: String.t() - def collection_query_params(options) do - params = - options - |> Keyword.take([:limit, :skip, :content_type, :include]) - |> URI.encode_query() - - "?#{params}" - end - @doc """ Parses the response from the CDA and triggers a callback on success """ @@ -346,62 +284,19 @@ defmodule Contentful.Delivery do {:error, :unknown} end - defp authorization_header(token) when is_nil(token) do - api_key_from_configuration() |> authorization_header() - end - - defp authorization_header(token) do - [authorization: "Bearer #{token}"] - end - - defp api_key_from_configuration do - config(:api_key) |> fallback("") - end - defp environment_from_config do - config(:environment) |> fallback("master") + Configuration.get(:environment) |> fallback("master") end defp space_from_config do - config(:space) + Configuration.get(:space) end defp host_from_config do - case config(:endpoint) do + case Configuration.get(:endpoint) do nil -> @endpoint :preview -> @preview_endpoint value -> value end end - - @doc """ - Can be used to retrieve configuration for the `Contentful.Delivery` module - - ## Examples - config :contentful, delivery: [ - my_config: "foobar" - ] - - "foobar" = Contentful.Delivery.config(:my_config) - """ - @spec config(atom()) :: any() - def config(setting) do - config() |> Keyword.get(setting) - end - - @doc """ - loads the configuration for the delivery module from the contentful app configuration - """ - @spec config() :: list(keyword()) - def config do - Application.get_env(:contentful, :delivery, []) - end - - defp fallback(nil, value) do - value - end - - defp fallback(value, _) do - value - end end diff --git a/lib/contentful_delivery/entries.ex b/lib/contentful_delivery/entries.ex index 51cb4da..2f40818 100644 --- a/lib/contentful_delivery/entries.ex +++ b/lib/contentful_delivery/entries.ex @@ -117,7 +117,7 @@ defmodule Contentful.Delivery.Entries do "updatedAt" => updated_at, "createdAt" => created_at, "locale" => locale, - "contentType" => %{"sys" => content_type_id} + "contentType" => %{"sys" => %{"id" => content_type_id}} } }) do {:ok, diff --git a/mix.exs b/mix.exs index 1674da0..93ad785 100644 --- a/mix.exs +++ b/mix.exs @@ -103,6 +103,7 @@ defmodule Contentful.Mixfile do {:dialyxir, "~> 1.0.0-rc.7", only: [:dev], runtime: false}, {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, {:excoveralls, "~> 0.10", only: :test}, + {:mix_test_watch, "~> 1.0", only: :dev, runtime: false}, # docs {:inch_ex, "2.0.0", only: :docs} diff --git a/mix.lock b/mix.lock index 664ae99..ad9537a 100644 --- a/mix.lock +++ b/mix.lock @@ -11,6 +11,7 @@ "excoveralls": {:hex, :excoveralls, "0.13.0", "4e1b7cc4e0351d8d16e9be21b0345a7e165798ee5319c7800b9138ce17e0b38e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "fe2a56c8909564e2e6764765878d7d5e141f2af3bc8ff3b018a68ee2a218fced"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, "exvcr": {:hex, :exvcr, "0.11.1", "a5e5f57a67538e032e16cfea6cfb1232314fb146e3ceedf1cde4a11f12fb7a58", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "984a4d52d9e01d5f0e28d45718565a41dffab3ac18e029ae45d42f16a2a58a1d"}, + "file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"}, "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, "httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"}, "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, @@ -22,6 +23,7 @@ "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, diff --git a/test/contentful/misc_test.exs b/test/contentful/misc_test.exs new file mode 100644 index 0000000..a869850 --- /dev/null +++ b/test/contentful/misc_test.exs @@ -0,0 +1,5 @@ +defmodule Contentful.MiscTest do + use ExUnit.Case + + doctest Contentful.Misc +end diff --git a/test/contentful/query_test.exs b/test/contentful/query_test.exs index 0050f57..10558c6 100644 --- a/test/contentful/query_test.exs +++ b/test/contentful/query_test.exs @@ -23,4 +23,41 @@ defmodule Contentful.QueryTest do {Entries, include: 2, limit: 4} = Entries |> Query.limit(4) |> Query.include(2) end end + + describe "by/2" do + test "throws an error when used for entries without a content_type call before" do + assert_raise(ArgumentError, fn -> + Entries |> Query.by(id: "foobar") + end) + end + + test "throws no error when used for entries with a content_type" do + {Entries, + [ + {:select_params, [id: "foobar"]}, + {:content_type, "car"} + ]} = Entries |> Query.content_type("car") |> Query.by(id: "foobar") + end + + test "allows passing multiple fields into it" do + {Entries, + [ + select_params: [id: "foobar", name: [ne: "Mercedes"]], + content_type: "car" + ]} = + Entries + |> Query.content_type("car") + |> Query.by(id: "foobar", name: [ne: "Mercedes"]) + end + end + + describe "search_full_text/2" do + test "adds a query to the parameters" do + {Entries, [query: "foobar"]} = Entries |> Query.search_full_text("foobar") + end + + test "gets ignored for everything other than Entries" do + Assets = Assets |> Query.search_full_text("barfoo") + end + end end diff --git a/test/contentful_delivery/assets_test.exs b/test/contentful_delivery/assets_test.exs index 51af6eb..2a67f05 100644 --- a/test/contentful_delivery/assets_test.exs +++ b/test/contentful_delivery/assets_test.exs @@ -8,7 +8,9 @@ defmodule Contentful.Delivery.AssetsTest do import Contentful.Query @space_id "bmehzfuz4raf" + @env "master" @asset_id "577fpmbIfYD71VCjCpYA84" + @access_token nil setup_all do HTTPoison.start() @@ -49,26 +51,44 @@ defmodule Contentful.Delivery.AssetsTest do end end - test "will fetch all published entries for a space, respecting the limit parameter" do + test "will fetch all published assets for a space, respecting the limit parameter" do use_cassette "multiple assets, limit filter" do {:ok, [%Asset{fields: %{title: "bafoo"}}], total: 2} = Assets |> limit(1) |> fetch_all(@space_id) end end - test "will fetch all published entries for a space, respecting the skip param" do + test "will fetch all published assets for a space, respecting the skip param" do use_cassette "multiple assets, skip filter" do {:ok, [%Asset{fields: %{title: "Foobar"}}], total: 2} = Assets |> skip(1) |> fetch_all(@space_id) end end - test "will fetch fetch all published entries for a space, respecting both the skip and the limit param" do + test "will fetch fetch all published assets for a space, respecting both the skip and the limit param" do use_cassette "multiple assets, all filters" do {:ok, [%Asset{fields: %{title: "Foobar"}}], total: 2} = Assets |> limit(1) |> skip(1) |> fetch_all(@space_id) end end + + test "will fetch all published assets, filtered by a name" do + use_cassette "multiple assets, filtered by name" do + {:ok, [%Asset{fields: %{title: "bafoo"}}], total: 1} = + Assets + |> by(title: "bafoo") + |> fetch_all(@space_id, @env, @access_token) + end + end + + test "will fetch all published assets, filtered by a name, negated" do + use_cassette "multiple assets, filtered by name, negated" do + {:ok, [%Asset{fields: %{title: nil}}], total: 1} = + Assets + |> by(title: [ne: "bafoo"]) + |> fetch_all(@space_id, @env, @access_token) + end + end end describe ".stream" do diff --git a/test/contentful_delivery/delivery_test.exs b/test/contentful_delivery/delivery_test.exs index c2900a5..21b53eb 100644 --- a/test/contentful_delivery/delivery_test.exs +++ b/test/contentful_delivery/delivery_test.exs @@ -35,14 +35,4 @@ defmodule Contentful.DeliveryTest do Delivery.url("foobar", "baz") end end - - describe ".request_headers" do - test "will construct the necessary headers for making a request with an api token" do - assert [ - authorization: "Bearer api_access_token", - "User-Agent": "Contentful Elixir SDK", - accept: "application/json" - ] == "api_access_token" |> Delivery.request_headers() - end - end end diff --git a/test/contentful_delivery/entries_test.exs b/test/contentful_delivery/entries_test.exs index adb4e0a..0b1597d 100644 --- a/test/contentful_delivery/entries_test.exs +++ b/test/contentful_delivery/entries_test.exs @@ -2,7 +2,7 @@ defmodule Contentful.Delivery.EntriesTest do use ExUnit.Case alias Contentful.Delivery.Entries - alias Contentful.{Entry, Space, SysData} + alias Contentful.{ContentType, Entry, Space, SysData} use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney @@ -79,7 +79,7 @@ defmodule Contentful.Delivery.EntriesTest do end end - test "will fetch fetch all published entries for a space, respecting both the skip and the limit param" do + test "will fetch all published entries for a space, respecting both the skip and the limit param" do use_cassette "multiple entries, all filters" do {:ok, [%Entry{fields: %{"name" => "Blue steel"}}], total: 2} = Entries @@ -92,5 +92,55 @@ defmodule Contentful.Delivery.EntriesTest do ) end end + + test "will fetch all published entries by spaces, filtered by content_type" do + use_cassette "multiple entries, filtered by content_type" do + {:ok, + [ + %Entry{ + sys: %SysData{id: "7qCGg4LadgJUcx5cr35Ou9", content_type: %ContentType{id: "category"}} + }, + %Entry{ + sys: %SysData{id: "4RPjazUzQMqemyNlcD3b9i", content_type: %ContentType{id: "category"}} + } + ], + total: 2} = + Entries + |> content_type("category") + |> fetch_all(@space_id, @env, @access_token) + end + end + + test "will support select as a way of selecting sys.id" do + use_cassette "single entry with select filters" do + {:ok, + [ + %Entry{ + sys: %SysData{id: "7qCGg4LadgJUcx5cr35Ou9", content_type: %ContentType{id: "category"}} + } + ], + total: 1} = + Entries + |> content_type("category") + |> by(id: "7qCGg4LadgJUcx5cr35Ou9") + |> fetch_all(@space_id, @env, @access_token) + end + end + + test "will support negated filtering by field" do + use_cassette "some entries not having an id" do + {:ok, + [ + %Entry{ + sys: %SysData{id: "4RPjazUzQMqemyNlcD3b9i", content_type: %ContentType{id: "category"}} + } + ], + total: 1} = + Entries + |> content_type("category") + |> by(id: [ne: "7qCGg4LadgJUcx5cr35Ou9"]) + |> fetch_all(@space_id, @env, @access_token) + end + end end end diff --git a/test/contentful_delivery/request_test.exs b/test/contentful_delivery/request_test.exs new file mode 100644 index 0000000..f60a0e6 --- /dev/null +++ b/test/contentful_delivery/request_test.exs @@ -0,0 +1,44 @@ +defmodule Contentful.RequestTest do + use ExUnit.Case + + alias Contentful.Request + + doctest Contentful.Request + + describe "headers/1" do + test "will construct the necessary headers for making a request with an api token" do + assert [ + authorization: "Bearer api_access_token", + "User-Agent": "Contentful Elixir SDK", + "X-Contentful-User-Agent": "Contentful Elixir SDK", + accept: "application/json" + ] == "api_access_token" |> Request.headers() + end + end + + describe "collection_query_params/1" do + test "omits arbitrary keywords" do + [limit: 1, skip: 2] = Request.collection_query_params(limit: 1, skip: 2) + end + + test "raises an error for unknown modifiers" do + assert_raise(ArgumentError, fn -> + Request.collection_query_params(select_params: [id: [foo: "bar"]]) + end) + end + + test "translates id into sys.id" do + ["sys.id": "foo"] = Request.collection_query_params(select_params: [id: "foo"]) + end + + test "supports modifiers" do + ["fields.name[ne]": "bar"] = + Request.collection_query_params(select_params: [name: [ne: "bar"]]) + end + + test "supports translation of array properties" do + ["fields.tags[nin]": "foo,bar,barfoo"] = + Request.collection_query_params(select_params: [tags: [nin: ["foo", "bar", "barfoo"]]]) + end + end +end