View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2013-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.jcabi.github;
6   
7   import com.jcabi.aspects.Immutable;
8   import com.jcabi.aspects.Loggable;
9   import com.jcabi.http.Request;
10  import com.jcabi.http.response.JsonResponse;
11  import com.jcabi.http.response.RestResponse;
12  import jakarta.json.Json;
13  import jakarta.json.JsonObject;
14  import jakarta.json.JsonStructure;
15  import jakarta.json.JsonValue;
16  import java.io.IOException;
17  import java.net.HttpURLConnection;
18  import lombok.EqualsAndHashCode;
19  
20  /**
21   * GitHub contents.
22   * @since 0.8
23   * @checkstyle MultipleStringLiteralsCheck (300 lines)
24   */
25  @Immutable
26  @Loggable(Loggable.DEBUG)
27  @EqualsAndHashCode(of = { "entry", "request", "owner" })
28  @SuppressWarnings("PMD.AvoidDuplicateLiterals")
29  final class RtContents implements Contents {
30  
31      /**
32       * API entry point.
33       */
34      private final transient Request entry;
35  
36      /**
37       * Repository.
38       */
39      private final transient Repo owner;
40  
41      /**
42       * RESTful request.
43       */
44      private final transient Request request;
45  
46      /**
47       * Public ctor.
48       * @param req RESTful API entry point
49       * @param repo Repository
50       */
51      RtContents(final Request req, final Repo repo) {
52          this.entry = req;
53          this.owner = repo;
54          this.request = req.uri()
55              .path("/repos")
56              .path(repo.coordinates().user())
57              .path(repo.coordinates().repo())
58              .path("/contents")
59              .back();
60      }
61  
62      @Override
63      public Repo repo() {
64          return this.owner;
65      }
66  
67      @Override
68      public Content readme() throws IOException {
69          return new RtContent(
70              this.entry, this.owner,
71              this.entry.uri()
72                  .path("/repos")
73                  .path(this.owner.coordinates().user())
74                  .path(this.owner.coordinates().repo())
75                  .path("/readme")
76                  .back()
77                  .method(Request.GET)
78                  .fetch()
79                  .as(RestResponse.class)
80                  .assertStatus(HttpURLConnection.HTTP_OK)
81                  .as(JsonResponse.class)
82                  .json().readObject().getString("path")
83          );
84      }
85  
86      @Override
87      public Content readme(
88          final String branch
89      ) throws IOException {
90          final JsonStructure json = Json.createObjectBuilder()
91              .add("ref", branch)
92              .build();
93          return new RtContent(
94              this.entry, this.owner,
95              this.entry.uri()
96                  .path("/repos")
97                  .path(this.owner.coordinates().user())
98                  .path(this.owner.coordinates().repo())
99                  .path("/readme")
100                 .back()
101                 .method(Request.GET)
102                 .body().set(json).back()
103                 .fetch()
104                 .as(RestResponse.class)
105                 .assertStatus(HttpURLConnection.HTTP_OK)
106                 .as(JsonResponse.class)
107                 .json().readObject().getString("path")
108         );
109     }
110 
111     @Override
112     public Content create(
113         final JsonObject content
114     )
115         throws IOException {
116         if (!content.containsKey("path")) {
117             throw new IllegalStateException(
118                 "Content should have path parameter"
119             );
120         }
121         final String path = content.getString("path");
122         return new RtContent(
123             this.entry, this.owner,
124             this.request.method(Request.PUT)
125                 .uri().path(path).back()
126                 .body().set(content).back()
127                 .fetch()
128                 .as(RestResponse.class)
129                 .assertStatus(HttpURLConnection.HTTP_CREATED)
130                 .as(JsonResponse.class)
131                 .json().readObject().getJsonObject("content").getString("path")
132         );
133     }
134 
135     @Override
136     public Content get(
137         final String path,
138         final String ref
139     ) throws IOException {
140         return this.content(path, ref);
141     }
142 
143     @Override
144     public Content get(
145         final String path
146     ) throws IOException {
147         return this.content(path, this.repo().defaultBranch().name());
148     }
149 
150     @Override
151     public Iterable<Content> iterate(
152         final String path,
153         final String ref
154     ) {
155         return new RtPagination<>(
156             this.request.method(Request.GET)
157                 .uri().path(path).queryParam("ref", ref).back(),
158             object -> new RtContent(
159                 this.entry, this.owner,
160                 object.getString("path")
161             )
162         );
163     }
164 
165     @Override
166     public RepoCommit remove(final JsonObject content
167     )
168         throws IOException {
169         if (!content.containsKey("path")) {
170             throw new IllegalStateException(
171                 "Content should have path parameter"
172             );
173         }
174         final String path = content.getString("path");
175         return new RtRepoCommit(
176             this.entry,
177             this.owner,
178             this.request.method(Request.DELETE)
179                 .uri().path(path).back()
180                 .body().set(content).back().fetch()
181                 .as(RestResponse.class)
182                 .assertStatus(HttpURLConnection.HTTP_OK)
183                 .as(JsonResponse.class).json()
184                 .readObject().getJsonObject("commit").getString("sha")
185         );
186     }
187 
188     @Override
189     public RepoCommit update(
190         final String path,
191         final JsonObject json)
192         throws IOException {
193         return new RtRepoCommit(
194             this.entry,
195             this.owner,
196             this.request.uri().path(path).back()
197                 .method(Request.PUT)
198                 .body().set(json).back()
199                 .fetch()
200                 .as(RestResponse.class)
201                 .assertStatus(HttpURLConnection.HTTP_OK)
202                 .as(JsonResponse.class).json()
203                 .readObject().getJsonObject("commit").getString("sha")
204         );
205     }
206 
207     @Override
208     public boolean exists(final String path, final String ref)
209         throws IOException {
210         final RestResponse response = this.request.method(Request.GET)
211             .uri().path(path).queryParam("ref", ref).back()
212             .fetch().as(RestResponse.class);
213         return response.status() == HttpURLConnection.HTTP_OK;
214     }
215 
216     /**
217      * Get the contents of a file or symbolic link in a repository.
218      * @param path The content path
219      * @param ref The name of the commit/branch/tag.
220      * @return Content fetched
221      * @throws IOException If there is any I/O problem
222      * @see <a href="https://developer.github.com/v3/repos/contents/#get-contents">Get contents</a>
223      */
224     private Content content(
225         final String path, final String ref
226     ) throws IOException {
227         final String name = "ref";
228         RtContent content = null;
229         final JsonStructure structure = this.request.method(Request.GET)
230             .uri().path(path).queryParam(name, ref).back()
231             .fetch()
232             .as(RestResponse.class)
233             .assertStatus(HttpURLConnection.HTTP_OK)
234             .as(JsonResponse.class)
235             .json().read();
236         if (JsonValue.ValueType.OBJECT.equals(structure.getValueType())) {
237             content = new RtContent(
238                 this.entry.uri().queryParam(name, ref).back(), this.owner,
239                 ((JsonObject) structure).getString("path")
240             );
241         }
242         return content;
243     }
244 
245 }