Skip to content
This repository has been archived by the owner on Aug 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #160 from unsplash/feature/response-types
Browse files Browse the repository at this point in the history
Feature: Response types
  • Loading branch information
samijaber authored Jan 21, 2021
2 parents 4e490ec + ca574fa commit 58945e5
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 7.0.3

- Adds response types to all endpoints.

## 7.0.0

This version includes a total TypeScript rewrite of the library, with many breaking changes. If upgrading from a previous version, read carefully. You will not be able to upgrade to v7 without making the necessary adjustments.
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Official Javascript wrapper for the [Unsplash API](https://unsplash.com/developers).

Key Links:

- Before using the Unsplash API, [register as a developer](https://unsplash.com/developers).
- Before using the Unsplash API, read the [API Guidelines](https://help.unsplash.com/api-guidelines/unsplash-api-guidelines). Specifically, you _must_:
- [hotlink images](https://help.unsplash.com/api-guidelines/more-on-each-guideline/guideline-hotlinking-images)
Expand Down Expand Up @@ -76,7 +77,7 @@ This library also depends on the WHATWG URL interface:
- MDN [docs](https://developer.mozilla.org/en-US/docs/Web/API/URL) for browsers.
- NodeJS [docs](https://nodejs.org/api/url.html).

Note: Make sure to polyfill this interface if targetting older environments that do not implement it (i.e. Internet Explorer or Node < v8).
Note: Make sure to polyfill this interface if targetting older environments that do not implement it (i.e. Internet Explorer or Node < v8).

Note 2: For Node, the URL interface exists under `require('url').URL` since [v8](https://nodejs.org/es/blog/release/v8.0.0/#say-hello-to-the-whatwg-url-parser) but was only added to the global scope as of [v10.0.0](https://nodejs.org/docs/latest/api/globals.html#globals_url). If you are using a version between v8.0.0 and v10.0.0, you need to add the class to the global scope before using `unsplash-js`:

Expand Down Expand Up @@ -209,9 +210,15 @@ unsplash.photos.get({ photoId: 'foo' }).then(result => {

This library is written in TypeScript. This means that even if you are writing plain JavaScript, you can still get useful and accurate type information. We highly recommend that you setup your environment (using an IDE such as [VSCode](https://code.visualstudio.com/)) to fully benefit from this information:

### Function arguments

![](./vscode-screenshot2.png)
![](./vscode-screenshot.png)

### Response Types

![](./vscode-response-types.png)

## Instance Methods

NOTE: All of the method arguments described here are in the first parameter. See the [arguments](#Arguments) section for more information.
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ export const createApi = flow(initMakeRequest, makeRequest => ({
},
}));

export { Language, ColorId, ContentFilter, SearchOrderBy } from './methods/search/types';
export { Language, ColorId, ContentFilter, SearchOrderBy } from './methods/search/types/request';
export { OrderBy, Orientation } from './types/request';
export { _internals };
28 changes: 28 additions & 0 deletions src/methods/collections/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Nullable } from '../../helpers/typescript';
import { Entity } from '../../types/entities';
import * as Photo from '../photos/types';
import * as User from '../users/types';

export interface Basic extends Entity {
cover_photo: Nullable<Photo.Basic>;
description: Nullable<string>;
featured: boolean;
/**
* This is different from `updated_at` because that may also change when a photo inside changes or
* is deleted.
*/
last_collected_at: string;
links: {
self: string;
html: string;
photos: string;
download?: string;
related?: string;
};
preview_photos: Nullable<Photo.VeryBasic[]>;
published_at: string;
title: string;
total_photos: number;
updated_at: string;
user: User.Basic;
}
11 changes: 6 additions & 5 deletions src/methods/photos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { castResponse } from '../../helpers/response';
import { isDefined } from '../../helpers/typescript';
import { parseQueryAndPathname } from '../../helpers/url';
import { OrientationParam, PaginationParams } from '../../types/request';
import * as Photo from './types';

type PhotoId = {
photoId: string;
Expand All @@ -18,23 +19,23 @@ export const list = {
pathname: PHOTOS_PATH_PREFIX,
query: compactDefined(Query.getFeedParams(feedParams)),
})),
handleResponse: handleFeedResponse<any>(),
handleResponse: handleFeedResponse<Photo.Basic>(),
};

export const get = {
handleRequest: createRequestHandler(({ photoId }: PhotoId) => ({
pathname: `${PHOTOS_PATH_PREFIX}/${photoId}`,
query: {},
})),
handleResponse: castResponse<any>(),
handleResponse: castResponse<Photo.Full>(),
};

export const getStats = {
handleRequest: createRequestHandler(({ photoId }: PhotoId) => ({
pathname: `${PHOTOS_PATH_PREFIX}/${photoId}/statistics`,
query: {},
})),
handleResponse: castResponse<any>(),
handleResponse: castResponse<Photo.Stats>(),
};

export const getRandom = {
Expand Down Expand Up @@ -62,7 +63,7 @@ export const getRandom = {
},
}),
),
handleResponse: castResponse<any>(),
handleResponse: castResponse<Photo.Random>(),
};

export const trackDownload = {
Expand All @@ -74,5 +75,5 @@ export const trackDownload = {
}
return { pathname, query: compactDefined(query) };
}),
handleResponse: castResponse<any>(),
handleResponse: castResponse<{ url: string }>(),
};
93 changes: 93 additions & 0 deletions src/methods/photos/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Nullable } from '../../helpers/typescript';
import { Entity } from '../../types/entities';
import * as Collection from '../collections/types';
import * as User from '../users/types';

interface StatValue {
value: number;
date: string;
}

interface Stat {
total: number;
historical: {
change: number;
quantity: number;
resolution: string;
values: StatValue[];
};
}

export interface Stats extends Entity {
views: Stat;
downloads: Stat;
}

export interface VeryBasic extends Entity {
created_at: string;
updated_at: string;
urls: {
full: string;
raw: string;
regular: string;
small: string;
thumb: string;
};
}

export interface Basic extends VeryBasic {
alt_description: Nullable<string>;
blur_hash: Nullable<string>;
color: Nullable<string>;
description: Nullable<string>;
height: number;
likes: number;
links: {
self: string;
html: string;
download: string;
download_location: string;
};
promoted_at: Nullable<string>;
width: number;
user: User.Basic;
}

interface ExifAndLocation {
exif: {
make: Nullable<string>;
model: Nullable<string>;
exposure_time: Nullable<string>;
aperture: Nullable<string>;
focal_length: Nullable<string>;
iso: Nullable<number>;
};
location: {
city: Nullable<string>;
country: Nullable<string>;

/** full string representation of the location, including `city` and `country` if they exist. */
name: Nullable<string>;

position: {
latitude: Nullable<number>;
longitude: Nullable<number>;
};
};
}

export interface Random extends Basic, ExifAndLocation {}

type RelatedCollectionsType =
// Ambiguously related collections
| 'related'
// Collections the photo belongs to
| 'collected';

export interface Full extends Basic, ExifAndLocation {
related_collections: {
type: RelatedCollectionsType;
results: Collection.Basic[];
total: number;
};
}
9 changes: 5 additions & 4 deletions src/methods/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import * as Query from '../../helpers/query';
import { createRequestHandler } from '../../helpers/request';
import { castResponse } from '../../helpers/response';
import { OrientationParam, PaginationParams } from '../../types/request';
import { ColorId, ContentFilter, Language, SearchOrderBy } from './types';
import { ColorId, ContentFilter, Language, SearchOrderBy } from './types/request';
import * as SearchResponse from './types/response';

export type SearchParams = {
query: string;
Expand Down Expand Up @@ -53,21 +54,21 @@ export const getPhotos = {
}),
}),
),
handleResponse: castResponse<any>(),
handleResponse: castResponse<SearchResponse.Photos>(),
};

export const getCollections = {
handleRequest: createRequestHandler(({ query, ...paginationParams }: SearchParams) => ({
pathname: `${SEARCH_PATH_PREFIX}/collections`,
query: { query, ...Query.getFeedParams(paginationParams) },
})),
handleResponse: castResponse<any>(),
handleResponse: castResponse<SearchResponse.Collections>(),
};

export const getUsers = {
handleRequest: createRequestHandler(({ query, ...paginationParams }: SearchParams) => ({
pathname: `${SEARCH_PATH_PREFIX}/users`,
query: { query, ...Query.getFeedParams(paginationParams) },
})),
handleResponse: castResponse<any>(),
handleResponse: castResponse<SearchResponse.Users>(),
};
File renamed without changes.
13 changes: 13 additions & 0 deletions src/methods/search/types/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as CollectionApi from '../../collections/types';
import * as PhotoApi from '../../photos/types';
import * as UserApi from '../../users/types';

interface Response<A> {
results: A[];
total: number;
total_pages: number;
}

export interface Photos extends Response<PhotoApi.Basic> {}
export interface Collections extends Response<CollectionApi.Basic> {}
export interface Users extends Response<UserApi.Medium> {}
11 changes: 7 additions & 4 deletions src/methods/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as Query from '../../helpers/query';
import { createRequestHandler } from '../../helpers/request';
import { castResponse } from '../../helpers/response';
import { OrientationParam, PaginationParams } from '../../types/request';
import * as User from './types';
import * as Photo from '../photos/types';
import * as Collection from '../collections/types';

type UserName = {
username: string;
Expand All @@ -16,7 +19,7 @@ export const get = {
pathname: `${USERS_PATH_PREFIX}/${username}`,
query: {},
})),
handleResponse: castResponse<any>(),
handleResponse: castResponse<User.Full>(),
};

export const getPhotos = {
Expand All @@ -39,7 +42,7 @@ export const getPhotos = {
}),
}),
),
handleResponse: handleFeedResponse<any>(),
handleResponse: handleFeedResponse<Photo.Basic>(),
};

export const getLikes = {
Expand All @@ -56,7 +59,7 @@ export const getLikes = {
}),
}),
),
handleResponse: handleFeedResponse<any>(),
handleResponse: handleFeedResponse<Photo.Basic>(),
};
export const getCollections = {
handleRequest: createRequestHandler(
Expand All @@ -65,5 +68,5 @@ export const getCollections = {
query: Query.getFeedParams(paginationParams),
}),
),
handleResponse: handleFeedResponse<any>(),
handleResponse: handleFeedResponse<Collection.Basic>(),
};
43 changes: 43 additions & 0 deletions src/methods/users/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Nullable } from '../../helpers/typescript';
import { Entity } from '../../types/entities';
import * as Photo from '../photos/types';

export interface Basic extends Entity {
bio: Nullable<string>;
first_name: string;
instagram_username: Nullable<string>;
last_name: Nullable<string>;
links: {
followers: string;
following: string;
html: string;
likes: string;
photos: string;
portfolio: string;
self: string;
};
location: Nullable<string>;
name: string;
portfolio_url: Nullable<string>;
profile_image: {
small: string;
medium: string;
large: string;
};
total_collections: number;
total_likes: number;
total_photos: number;
twitter_username: Nullable<string>;
updated_at: string;
username: string;
}

export interface Medium extends Basic {
photos: Photo.VeryBasic[];
}

export interface Full extends Medium {
downloads: number;
followers_count: number;
following_count: number;
}
3 changes: 3 additions & 0 deletions src/types/entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Entity {
id: string;
}
2 changes: 1 addition & 1 deletion tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as search from '../src/methods/search';
import * as users from '../src/methods/users';
import { createApi } from '../src';
import { OrderBy } from '../src/types/request';
import { Language } from '../src/methods/search/types';
import { Language } from '../src/methods/search/types/request';
import { buildUrl, parseQueryAndPathname } from '../src/helpers/url';

describe('parseQueryAndPathname', () => {
Expand Down
Binary file added vscode-response-types.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 58945e5

Please sign in to comment.