Skip to content
This repository was archived by the owner on Oct 25, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CshapStudy.DtoMapper/Common/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CshapStudy.DtoMapper.Common;

public abstract record Result<TData,TError>
{
public sealed record Success(TData data) : Result<TData, TError>;

public sealed record Error(TError error) : Result<TData, TError>;
}
14 changes: 14 additions & 0 deletions CshapStudy.DtoMapper/CshapStudy.DtoMapper.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

</Project>
286 changes: 286 additions & 0 deletions CshapStudy.DtoMapper/DTOs/PokemonDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
using Newtonsoft.Json;

namespace CshapStudy.DtoMapper.DTOs
{
public class PokemonDto
{
[JsonProperty("abilities")]
public List<AbilitySlotDto>? Abilities { get; set; }

[JsonProperty("base_experience")]
public int? BaseExperience { get; set; }

[JsonProperty("cries")]
public CriesDto? Cries { get; set; }

[JsonProperty("forms")]
public List<NamedApiResourceDto>? Forms { get; set; }

[JsonProperty("game_indices")]
public List<GameIndexDto>? GameIndices { get; set; }

[JsonProperty("height")]
public int? Height { get; set; }

[JsonProperty("held_items")]
public List<HeldItemDto>? HeldItems { get; set; }

[JsonProperty("id")]
public int? Id { get; set; }

[JsonProperty("is_default")]
public bool? IsDefault { get; set; }

[JsonProperty("location_area_encounters")]
public string? LocationAreaEncounters { get; set; }

[JsonProperty("moves")]
public List<MoveDto>? Moves { get; set; }

[JsonProperty("name")]
public string? Name { get; set; }

[JsonProperty("order")]
public int? Order { get; set; }

[JsonProperty("past_abilities")]
public List<PastAbilityDto>? PastAbilities { get; set; }

[JsonProperty("past_types")]
public List<object>? PastTypes { get; set; }

[JsonProperty("species")]
public NamedApiResourceDto? Species { get; set; }

[JsonProperty("sprites")]
public SpritesDto? Sprites { get; set; }

[JsonProperty("stats")]
public List<StatDto>? Stats { get; set; }

[JsonProperty("types")]
public List<TypeDto>? Types { get; set; }

[JsonProperty("weight")]
public int? Weight { get; set; }
}

// --- 중첩된 DTO 클래스들 ---

public class AbilitySlotDto
{
[JsonProperty("ability")]
public NamedApiResourceDto? Ability { get; set; }

[JsonProperty("is_hidden")]
public bool? IsHidden { get; set; }

[JsonProperty("slot")]
public int? Slot { get; set; }
}

public class CriesDto
{
[JsonProperty("latest")]
public string? Latest { get; set; }

[JsonProperty("legacy")]
public string? Legacy { get; set; }
}

public class GameIndexDto
{
[JsonProperty("game_index")]
public int? GameIndex { get; set; }

[JsonProperty("version")]
public NamedApiResourceDto? Version { get; set; }
}

public class HeldItemDto
{
[JsonProperty("item")]
public NamedApiResourceDto? Item { get; set; }

[JsonProperty("version_details")]
public List<VersionDetailDto>? VersionDetails { get; set; }
}

public class VersionDetailDto
{
[JsonProperty("rarity")]
public int? Rarity { get; set; }

[JsonProperty("version")]
public NamedApiResourceDto? Version { get; set; }
}

public class MoveDto
{
[JsonProperty("move")]
public NamedApiResourceDto? Move { get; set; }

[JsonProperty("version_group_details")]
public List<MoveLearnMethodDto>? VersionGroupDetails { get; set; }
}

public class MoveLearnMethodDto
{
[JsonProperty("level_learned_at")]
public int? LevelLearnedAt { get; set; }

[JsonProperty("move_learn_method")]
public NamedApiResourceDto? MoveLearnMethod { get; set; }

[JsonProperty("version_group")]
public NamedApiResourceDto? VersionGroup { get; set; }
}

public class PastAbilityDto
{
[JsonProperty("abilities")]
public List<AbilitySlotDto>? Abilities { get; set; }

[JsonProperty("generation")]
public NamedApiResourceDto? Generation { get; set; }
}

public class SpritesDto
{
[JsonProperty("back_default")]
public string? BackDefault { get; set; }

[JsonProperty("back_female")]
public string? BackFemale { get; set; } // Nullable string

[JsonProperty("back_shiny")]
public string? BackShiny { get; set; }

[JsonProperty("back_shiny_female")]
public string? BackShinyFemale { get; set; } // Nullable string

[JsonProperty("front_default")]
public string? FrontDefault { get; set; }

[JsonProperty("front_female")]
public string? FrontFemale { get; set; } // Nullable string

[JsonProperty("front_shiny")]
public string? FrontShiny { get; set; }

[JsonProperty("front_shiny_female")]
public string? FrontShinyFemale { get; set; } // Nullable string

[JsonProperty("other")]
public OtherSpritesDto? Other { get; set; }

[JsonProperty("versions")]
public Dictionary<string, object>? Versions { get; set; }
}

public class OtherSpritesDto
{
[JsonProperty("dream_world")]
public DreamWorldDto? DreamWorld { get; set; }

[JsonProperty("home")]
public HomeDto? Home { get; set; }

[JsonProperty("official-artwork")]
public OfficialArtworkDto? OfficialArtwork { get; set; }

[JsonProperty("showdown")]
public ShowdownDto? Showdown { get; set; }
}

public class DreamWorldDto
{
[JsonProperty("front_default")]
public string? FrontDefault { get; set; }

[JsonProperty("front_female")]
public string? FrontFemale { get; set; }
}

public class HomeDto
{
[JsonProperty("front_default")]
public string? FrontDefault { get; set; }

[JsonProperty("front_female")]
public string? FrontFemale { get; set; }

[JsonProperty("front_shiny")]
public string? FrontShiny { get; set; }

[JsonProperty("front_shiny_female")]
public string? FrontShinyFemale { get; set; }
}

public class OfficialArtworkDto
{
[JsonProperty("front_default")]
public string? FrontDefault { get; set; }

[JsonProperty("front_shiny")]
public string? FrontShiny { get; set; }
}

public class ShowdownDto
{
[JsonProperty("back_default")]
public string? BackDefault { get; set; }

[JsonProperty("back_female")]
public string? BackFemale { get; set; }

[JsonProperty("back_shiny")]
public string? BackShiny { get; set; }

[JsonProperty("back_shiny_female")]
public string? BackShinyFemale { get; set; }

[JsonProperty("front_default")]
public string? FrontDefault { get; set; }

[JsonProperty("front_female")]
public string? FrontFemale { get; set; }

[JsonProperty("front_shiny")]
public string? FrontShiny { get; set; }

[JsonProperty("front_shiny_female")]
public string? FrontShinyFemale { get; set; }
}

public class StatDto
{
[JsonProperty("base_stat")]
public int? BaseStat { get; set; }

[JsonProperty("effort")]
public int? Effort { get; set; }

[JsonProperty("stat")]
public NamedApiResourceDto? Stat { get; set; }
}

public class TypeDto
{
[JsonProperty("slot")]
public int? Slot { get; set; }

[JsonProperty("type")]
public NamedApiResourceDto? Type { get; set; }
}

// 이름과 URL을 가진 일반적인 리소스 객체
public class NamedApiResourceDto
{
[JsonProperty("name")]
public string? Name { get; set; }

[JsonProperty("url")]
public string? Url { get; set; }
}
}
8 changes: 8 additions & 0 deletions CshapStudy.DtoMapper/DataSource/IPokemonApiDataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using CshapStudy.DtoMapper.DTOs;

namespace CshapStudy.DtoMapper.DataSource;

public interface IPokemonApiDataSource
{
Task<Response<PokemonDto>> GetPokemonAsync(string pokemonName);
}
34 changes: 34 additions & 0 deletions CshapStudy.DtoMapper/DataSource/PokemonApiDataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Reflection.Metadata.Ecma335;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

불필요한 using 구문입니다. 코드의 가독성과 청결성을 위해 제거하는 것이 좋습니다.

using Newtonsoft.Json;


using CshapStudy.DtoMapper.DTOs;
namespace CshapStudy.DtoMapper.DataSource;

public class PokemonApiDataSource : IPokemonApiDataSource
{
private const string _BaseUrl = "https://pokeapi.co/api/v2/pokemon/";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

const 필드의 이름은 일반적으로 PascalCase(BaseUrl)를 따릅니다. C# 코딩 컨벤션에 따라 _BaseUrlBaseUrl로 변경하는 것을 제안합니다.

    private const string BaseUrl = "https://pokeapi.co/api/v2/pokemon/";

private HttpClient _httpClient;

public PokemonApiDataSource(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<Response<PokemonDto>> GetPokemonAsync(string pokemonName)
{
var response = await _httpClient.GetAsync($"{_BaseUrl}/{pokemonName}");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

pokemonName이 URL에 직접 삽입되고 있습니다. 만약 pokemonName/? 같은 특수 문자가 포함될 경우 URL이 깨질 수 있는 보안 취약점이 있습니다. Uri.EscapeDataString()을 사용하여 URL 인코딩을 해야 합니다.

        var response = await _httpClient.GetAsync($"{_BaseUrl}{Uri.EscapeDataString(pokemonName)}");

var jsonstring = await response.Content.ReadAsStringAsync();
var headers = response.Headers.ToDictionary(
header => header.Key,
header => string.Join(",", header.Value)
);

return new Response<PokemonDto>(
statusCode: (int)response.StatusCode,
headers: headers,
body: JsonConvert.DeserializeObject<PokemonDto>(jsonstring)!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

null-forgiving 연산자(!)는 잠재적인 null 값을 숨길 수 있어 위험합니다. JSON 역직렬화가 실패하면 null이 반환될 수 있으며, 이는 나중에 NullReferenceException을 유발할 것입니다. null을 확인하고 예외를 던지는 것이 더 안전한 방법입니다.

            body: JsonConvert.DeserializeObject<PokemonDto>(jsonstring) ?? throw new JsonException("Failed to deserialize PokemonDto.")

);
}

}
18 changes: 18 additions & 0 deletions CshapStudy.DtoMapper/DataSource/Response.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CshapStudy.DtoMapper.DTOs;

namespace CshapStudy.DtoMapper.DataSource;

public class Response<T>
{
public int StatusCode { get; set; }
public object Headers { get; set; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Headers 속성의 타입이 object로 되어 있어 타입 안정성이 떨어집니다. PokemonApiDataSource에서 Dictionary<string, string>으로 변환하고 있으므로, 이 타입을 IReadOnlyDictionary<string, string>으로 명시하는 것이 좋습니다.

    public IReadOnlyDictionary<string, string> Headers { get; set; }

public T Body { get; set; }

public Response(int statusCode, object headers, T body)
{
StatusCode = statusCode;
Headers = headers;
Body = body;
Comment on lines +7 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Response.Headers를 강타입으로 통일하고, 중복 타입을 제거하세요.

Headers가 object로 선언되어 있어 컴파일 타임 안전성이 없고, realtimeStationArrival.Common.Response와 불일치합니다. 공용 Response를 한 군데로 모으거나, 동일 시그니처(IReadOnlyDictionary<string,string>)로 통일해 주세요.

적용 예시:

 public class Response<T>
 {
     public int StatusCode { get; set; }
-    public object Headers { get; set; }
+    public IReadOnlyDictionary<string, string> Headers { get; set; }
     public T Body { get; set; }
 
-    public Response(int statusCode, object headers, T body)
+    public Response(int statusCode, IReadOnlyDictionary<string, string> headers, T body)
     {
         StatusCode = statusCode;
         Headers = headers; 
         Body = body;
     }
 }
🤖 Prompt for AI Agents
In CshapStudy.DtoMapper/DataSource/Response.cs around lines 7 to 15, the Headers
property and constructor parameter are currently object which lacks compile-time
safety and differs from realtimeStationArrival.Common.Response<T>; change the
Headers property type and constructor parameter to IReadOnlyDictionary<string,
string> (or the exact common Response<T> signature if available), update
assignments accordingly, and consolidate/remove duplicate Response<T>
definitions so both use the same strongly-typed
IReadOnlyDictionary<string,string> signature.


}
}
14 changes: 14 additions & 0 deletions CshapStudy.DtoMapper/Mappers/PokemonMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using CshapStudy.DtoMapper.DTOs;

namespace CshapStudy.DtoMapper.Mappers;

public static class PokemonMapper
{
public static Models.Pokemon ToModel(this PokemonDto dto)
{
return new Models.Pokemon(
Name: dto.Name ?? "",
ImageUrl: dto.Sprites?.Other?.OfficialArtwork?.FrontDefault ?? ""
);
}
}
Loading